Store experience as integers#799
Merged
Merged
Conversation
Contributor
Qodana Community for JVMIt seems all right 👌 No new problems were found according to the checks applied 💡 Qodana analysis was run in the pull request mode: only the changed files were checked Contact Qodana teamContact us at qodana-support@jetbrains.com
|
Codecov Report❌ Patch coverage is Additional details and impacted files
... and 9 files with indirect coverage changes @@ Coverage Diff @@
## main #799 +/- ##
============================================
+ Coverage 37.11% 37.17% +0.06%
- Complexity 7036 7058 +22
============================================
Files 1453 1455 +2
Lines 55865 55914 +49
Branches 13995 14007 +12
============================================
+ Hits 20734 20788 +54
+ Misses 30501 30491 -10
- Partials 4630 4635 +5
🚀 New features to boost your workflow:
|
Ilwyd
pushed a commit
to Ilwyd/void
that referenced
this pull request
Dec 22, 2025
* Support config reading doubles starting with decimal point * Convert experience to store integers instead of doubles * Fix tests * Fix database module * Fix
HarleyGilpin
added a commit
to HarleyGilpin/void
that referenced
this pull request
May 17, 2026
PR GregHib#799 added a Double->Int*10 conversion in PlayerSave's variables load branch to migrate old fractional XP saves into the new int experience format. The check was unconditional, so every Double variable got collapsed on load — including pet_*_hunger and pet_*_growth (declared format = "double"), which were turning into ints after every restart and then defaulting back to 0.0 on the next get<Double>() because Int can't be smart-cast to Double. Gate the conversion on VariableDefinitions.get(key)?.values being IntValues so it only fires for keys the current schema actually expects to be ints. Doubles for genuinely-double-typed vars now round-trip verbatim, which lets pet hunger / growth persist across server restarts.
GregHib
added a commit
that referenced
this pull request
Jun 10, 2026
* Feat: Address pet PR review feedback
- Drop IncubatorDefinitions; walk Tables/Rows for incubator eggs directly.
- Rename incubator state varbits to list-format with "empty/incubating/finished" labels; per-region end timer now a tick-based clock via start/remaining.
- Collapse pet item/npc handlers to single comma-separated ops; thin PetDefinitions to a lazy Tables wrapper.
- Replace the pet_stats blob with per-pet hunger/growth/warn vars; add DoubleValues so 0.0 defaults prune correctly.
- Open pet_details (663) instead of familiar_details for pet followers; dismiss handler now globs *_details.
- Stop the kitten in place when the Stroke option is used.
* Fix: route iface 663 Call/Dismiss by component, not option label
The pet_details (663) call/dismiss buttons emit cache option labels
distinct from familiar_details (662) ("Call Follower" / "Dismiss
Familiar" / "Dismiss Now"). The existing handlers keyed off those
exact strings and silently no-op'd on pets.
Wildcard the option label (`*:*_details:call` / `dismiss`) and branch
on follower-vs-pet inside. Pet dismiss always calls pickupPet so the
item is returned to inventory; familiar dismiss keeps the
confirm-vs-immediate distinction.
* Fix: pet Dismiss releases the pet instead of returning the item
Both pet_details:dismiss and the summoning_orb's right-click Dismiss
now call dismissPet, which removes the NPC and clears pet state
without putting the item back in inventory — the pet runs free. The
pet_details path wraps it in an "Are you sure you want to release your
pet?" confirmation since the item is gone for good; the orb's
right-click path stays a one-click action (matches the familiar
"Dismiss Now" semantics).
* Fix: pet kitten Stroke - cancel queued player walk
If the player had a minimap walk queued when they selected Stroke, the
player kept moving during the dialogue suspension while the kitten sat
in EmptyMode. When Follow was restored at the end of the interaction
the kitten had to sprint several tiles to catch up.
Clear the player's pending steps and face the kitten at the start of
the interaction so the player stays put for the animation.
* Fix: migrate pet code to weakQueue + fix Chase vermin
PR #980 deleted softQueue; replace the three pet/kitten call sites
with weakQueue (closest semantic match: easily-preempted fire-and-
forget) and drop the leftover `player.` prefixes inside the lambdas
since Player is now the lambda receiver.
While in KittenInteract, fix two bugs in chaseVermin:
- The cat used to chase pirates because "pirate" contains the
substring "rat". Predicate now matches the npc string id with a
`_` word boundary (rat / *_rat / rat_* / *_rat_*).
- The cat never caught anything because the 5-tick callback always
fired the failure message. Roll a 33% catch chance up front; on
success despawn the rat and message the win. Adjacency guard
covers the case where the rat wanders before the kitten arrives.
* Feat: chase-vermin dialogue + wiki-flavoured catch messages
Pre-chase dialogue runs before the kitten engages: player asks if it
wants to hunt, kitten replies, player tells it to take it easy. On
success the wiki "Hey well done puss, you got it!" / kitten
"MeeeoooooW!" pair fires, and every 10th catch logs the milestone
"Well done puss! N horrible rodents caught!". Caught count persists
on the player via pet_rats_caught.
* Feat: wiki cat dialogue, catspeak amulet variants
Branches every cat interaction on whether the player is wearing a
catspeak amulet and whether the pet is a kitten or adult cat.
Without amulet (kitten + adult cat):
- Stroke narrates "You softly stroke your cat." + "Purr...purr..." +
"The cat turns on its side while you bend down to pet it."
- Chase vermin keeps the existing 3-line pre-chase chatter and the
wiki success/fail/milestone copy.
- Shoo away gains the wiki "Are you sure you want to shoo away the
cat?" confirmation + "You choose not to shoo away the cat." cancel
message.
- Talk-to plays the simple "Hey puss! Any news?" / Purr / Meow lines.
- "There aren't any vermin around." message when no rat in scan radius.
With catspeak amulet (adult cat only):
- Stroke prepends Player "Who's a good cat then?" / Cat "Me, me.
Scratch me behind the ears." chathead exchange.
- Chase vermin uses the wiki adult opener "Go on get that nasty
rodent." / Cat "Yesss, food."
- Talk-to opens a 4-option chathead loop (how are you / how old /
where to / what to do) with a quit option.
- Pick-up plays "Come here furball." / Cat "Can we go adventuring
together again, soon?" / Player "Soon, I promise." before the item
returns to the inventory.
- Drop/Release plays "Hey cat, do you fancy stretching your legs..."
/ Cat "Miaaow, Are we going adventuring?" / "We'll see puss, we'll
see." before summoning.
KittenInteract registers Interact-with against every cat-like npc
(baby, grown, overgrown) so the menu is reachable past kittenhood.
PetScripts in Pet.kt routes the Pick-up / Drop / Release / Talk-to
operate handlers to the catspeak variants when the conditions match,
falling back to the generic pet handlers otherwise.
* Fix: move chaseVermin conversational lines to chathead
Three full-sentence lines were being emitted as overhead chat when
they should have been chathead dialogue:
- Cat's pre-chase reply "Meoowww. Yeah! Let's go kick some fur!"
was cat.say (overhead bubble); now an npc<Happy> chathead line.
- Player's success "Hey well done puss, you got it!" and the every-
10th-catch "Well done puss! N horrible rodents caught!" were
player overhead; now player<Happy> chathead.
Short imperative / sound-effect lines (Go on puss..., Shoo cat!,
Meeeoooooowwww!, Eek!, MeeeoooooW!) stay as overhead — those belong
above the speaker's head.
* Feat: extend cat drop dialogue to kittens + add no-amulet meow
- Catspeak "Hey cat, do you fancy stretching your legs..." exchange
now plays for every cat life stage (including kittens), not only
the adult/overgrown items.
- Dropping a cat without the amulet now plays a "Miaow!" overhead
one tick after the spawn settles (the new NPC is wired in
summonPet's own weakQueue at +2; we fire at +3 so pet?.say
targets the freshly summoned cat).
Folded the duplicated Drop/Release item-option bodies into a single
suspend helper Player.dropPet to keep the branching in one place.
* Feat: kitten pounce animation when catching the rat
Splits the chase-vermin weakQueue into a +4 pounce tick and a +5
resolve tick. When the cat has reached the rat (adjacency check) it
plays the new pet_pounce_kitten anim, then a tick later restores
Follow and resolves the catch/miss outcome.
The 9168 anim id is a best guess on the kitten anim grouping (stroke
is 9173) and may need swapping for one of 9167/9169-9172 after a
visual check in-game.
* Fix: correct kitten pounce anim id (9163, not 9168)
* Fix: scope legacy double->int variable migration to IntValues keys
PR #799 added a Double->Int*10 conversion in PlayerSave's variables
load branch to migrate old fractional XP saves into the new int
experience format. The check was unconditional, so every Double
variable got collapsed on load — including pet_*_hunger and
pet_*_growth (declared format = "double"), which were turning into
ints after every restart and then defaulting back to 0.0 on the
next get<Double>() because Int can't be smart-cast to Double.
Gate the conversion on VariableDefinitions.get(key)?.values being
IntValues so it only fires for keys the current schema actually
expects to be ints. Doubles for genuinely-double-typed vars now
round-trip verbatim, which lets pet hunger / growth persist across
server restarts.
* Feat: kitten faces the rat before pouncing
* Fix: kitten doesn't wander after Chase vermin gets interrupted
The chase resolve was wrapped in nested weakQueues, which the new
ActionQueue clears every time any Strong action enters the queue
(clicking another NPC/object, taking a hit, teleporting). When the
resolve dropped, the cat was left in EmptyMode and reverted to its
default idle/hunt wander.
Swap the two weakQueue calls in chaseVermin for queue (Normal
priority). Normal queues sit in the main queue list, only weakQueue
gets cleared on Strong, so the Follow restoration always fires —
even after a mid-chase interruption.
* Feat: second round of PR #983 review fixes
Merges upstream/main (PR #984 brings inc(max) and skill drop gating).
Engine reverts per Greg:
- Delete DoubleValues from VariableValues, plus its factory mapping.
- Remove both legacy double migrations from PlayerSave (variables loop
and the experience fractional path). Doubles are not used in any
player variable in the new schema.
Pet stats moved to integers on a 0..10000 scale:
- pet_state.vars.toml regenerated with format = "int".
- pets.tables.toml swaps growth_rate (double) for growth_per_tick
(int = rate * 50 * 100 per 30s tick).
- PetState drops the PetStats class and updatePetStats wrapper; the
getters return Int, callers use inc(key, amount, max = PET_STAT_MAX)
and dec(key, amount) directly (PR #984 added the max parameter).
- PetTimers thresholds become 7500 / 9000 / 10000 on the new scale,
HUNGER_BABY/GROWN become 125 / 90 per tick. PetFeeding feeds 1500.
- sendPetDetailsStats divides by 100 on the way out so the orb bars
stay on the 0..100 client scale.
PetDefinitions deleted; data lives in Tables now. The PetDefinition
data class is gone too. Pets.kt adds RowDefinition extensions
(isCatLike, stageForItem/Npc, npcFor/itemFor, nextStageItem/Npc,
isFinalStage, ambientPhrases) plus petRowForItem / petRowForNpc and
allPetRows lookup helpers. Every caller (Pet, KittenInteract,
PetTimers, PetFeeding) now consumes RowDefinition directly. The
Koin singleton in GameModules is gone.
Incubator suffix derivation collapses to
it.target.id.removePrefix("incubator_"). Renamed the per-region base
objects to incubator_taverley (28550) and incubator_yanille (28352);
kept the shared incubator_idle (28336) / incubator_active (28359)
transform names so the dispatch finds a registered string id for the
displayed mesh. target.id always reflects the base, so the suffix
extraction never sees idle/active. The regionVarbits map and the
tile.region.id helper are deleted.
KittenInteract:
- isRat simplifies to id.startsWith("rat") so giant_rat / warped_rat
drop out of the chase pool.
- talkToCatWithAmulet replaces the while(true) + keepGoing flag with
Greg's recursive-option pattern: each answer recurses back into
talkToCatWithAmulet, the quit option has no body.
pet.npcs.toml: every options = { ... } map deleted; npcs only need the
id field.
dragon.drops.toml: black dragon egg gated on skill = "summoning",
equals = 99 (PR #984 syntax).
Tests adapted: PetLogoutTest stats are ints; IncubatorUseEggTest uses
incubator_taverley for the test fixtures.
* Fix: cat stays put after Stroke interaction
The previous version swapped the cat into EmptyMode for the duration
of the dialogue and restored Follow afterwards. Restoring Follow
recalculates against player.steps.follow immediately, which queues a
step toward the player's last footprint tile and the cat ends up
walking a tile out then back in.
Use the movement_delay clock to suspend the cat's movement for the
length of the dialogue instead, then clear the clock plus any queued
steps at the end. Follow mode stays active throughout, so when the
clock releases there is nothing for the recalculation to fight and
the cat stays where it was being stroked.
* Feat: Talk-to dialogue for the Yanille / Taverley pet shop owners
Both pet shop owners (npc ids 6892 and 6893) share the same dialogue
tree per the wiki, so a single PetShopOwner script handles both with
one npcOperate matcher.
Main menu offers four options: open the pet shop (pet_shop), buy a
puppy, ask about other available pets, and sell spirit shards.
Puppy purchase guards against owning a dog already, runs the wiki
exchange about the 500 gold price, then either takes the coins and
hands over a default colour puppy or backs out gracefully on no
inventory space or insufficient coins. Six breeds are wired
(Bulldog / Dalmatian / Greyhound / Labrador / Sheepdog / Terrier).
The available-pets branch reproduces the wiki tree verbatim covering
nuts, birds + incubator, Karamja lizards, geckos / raccoons and the
banana-in-the-trap monkey tip.
Spirit shard sale uses intEntry for the count, drops the shards and
credits 25 coins each, with a graceful early-out when the player has
none on them.
Chathead expressions picked per line: Quiz for the player asking
questions, Neutral when accepting / refusing, Happy and Pleased for
the shop owner's friendlier or proud explanations.
* Feat: wire the Pick a puppy interface (id 668)
dialogue_pick_a_puppy in summoning.ifaces.toml now declares the six
breed components (.bulldog 3, .dalmatian 4, .greyhound 5, .terrier 6,
.sheepdog 7, .labrador 8). Ordering taken from 2009scape's
PuppyInterfacePlugin click map.
PetShopOwner opens the iface instead of running a kotlin choice() list
for breed selection, then suspends on pauseInt() and resumes against
the clicked component's index. continueDialogue handlers per breed
component resolve the suspension. Selecting a breed runs the existing
buyPuppy flow (price exchange, coin / inventory checks, puppy item
handed over).
BREEDS order rearranged to match the iface index map so
BREEDS.getOrNull(index) lands on the correct breed.
* Fix: Pick a puppy buttons fire via interfaceOption too
The cache buttons on iface 668 don't ship the continue-dialogue
packet, so the existing continueDialogue handlers never received the
click and the suspended caller stayed parked. Register each breed
component under interfaceOption as well so the InteractInterface
packet path also resumes the pauseInt; the continueDialogue
registration stays as a fallback for any client variant that does
ship the dialogue packet.
* Feat: Trade and Sell-shards options on both pet shop owners
* Fix: drop duplicate spirit-shards line on Talk-to choice option
The option<Quiz>() inline form echoes the option text as a player
chathead before invoking the block. spiritShards itself doesn't say
that line, but the choice menu shows the question and then the
player chathead repeated it a second time. Use the plain option()
form for the shards entry so the player chathead echo is skipped and
the conversation flows menu -> npc reply -> intEntry (or refusal).
* Fix: Sell-shards npc option also shows the player question line
I had the direction backwards: the Talk-to choice was correctly
echoing "Are you interested in buying spirit shards?" as a player
chathead, but the right-click Sell-shards path skipped straight to
the npc reply. Restored the option<Quiz> inline echo on the Talk-to
entry and added an explicit player<Quiz> line at the start of the
Sell-shards handler so both paths play the same beat:
player asks -> npc "I certainly am..." -> intEntry / refusal.
* Fix: incubator egg survives server restart + close double-summon race
Two restart-safety fixes for the pets feature.
Incubator state varbits now persist:
- persist = true on incubator_state_taverley / incubator_state_yanille
so the per-region state survives logout, instead of resetting to
"empty" on relog and making the inspect / take-egg / place-egg
paths think the incubator is empty even though the egg id is still
saved.
Incubator clock reset on relog:
- Player.playerSpawn now checks each suffix; if the egg is still on
file and the state is "incubating", restart the tick clock fresh
from the egg row's incubation_seconds. GameLoop.tick resets to 0
on each server boot so the saved deadline is no longer comparable
after a restart. Resetting the clock means the player loses any
on-server progress, but the egg itself is preserved.
Double-summon race in summonPet:
- pet_index was written in a +2 weakQueue, so two pet drops on the
same (or very next) tick both saw pet == null on the gate check
and spawned parallel NPCs, leaving one orphaned.
- Set pet synchronously now so the gate catches the second drop.
- Also tighten the gate with pet_active_item.isNotBlank() (only when
not restarting) so a follow-up regression in this area doesn't
silently slip past again. The relogin path passes restart=true
after clearing pet_index, so re-summon on login still works.
* Feat: kittens get lonely without attention
Cat-like baby pets (kittens) now track a loneliness stat alongside
hunger and growth. The counter ticks up 125 per 30s pet_tick on the
shared 0..10000 scale (40 minutes to run away). At 9000 (~36 min)
the player sees "Your kitten is feeling lonely. Pay it some attention
before it runs off." Hitting 10000 dispatches "Your kitten got lonely
and ran away." and despawns the kitten.
Resets to zero whenever the player interacts: Interact-with menu
opens (Stroke / Chase vermin / Shoo away all count) and Talk-to on a
kitten npc. Adult cats, hellcats, dogs, dragons etc. are unaffected
because tickLoneliness only fires for isCatLike + PetStage.Baby.
New persisted vars per cat-like row: pet_<id>_loneliness and
pet_<id>_lonely_warn. clearPetStats clears both alongside hunger /
growth / warn so a run-away or pickup wipes them too.
* Feat: wiki Talk-to dialogue for the six dog breeds
dog_talks.tables.toml carries every conversation per the wiki
transcripts (Bulldog, Dalmatian, Greyhound, Labrador, Sheepdog,
Terrier). One row per conversation with breed / stage / optional
inventory condition / a list of speaker-prefixed lines.
Speaker prefixes per line:
- b: overhead bark (dog.say) for puppy lines where the wiki shows
raw barks with no translation
- d: dog chathead (npc<Happy>) for adult lines that the wiki gives
in bark + parenthetical translation form
- p: player chathead (player<Happy>)
- no prefix: narrator chatbox via statement()
Pets.kt gains a RowDefinition.dogBreed() helper that collapses
colour suffixes ("bulldog_1", "bulldog_2") back to the canonical
breed name. Mirrors isCatLike().
DogTalk.kt holds the Player.talkToDog dispatcher: filters
dog_talks rows by breed + stage, prefers an inventory-conditional
branch when the player carries logs (Dalmatian), cup_of_tea
(Bulldog) or wool (Sheepdog), otherwise picks a random
unconditional row. Each line is rendered per its prefix.
Pet.kt's Talk-to handler now routes through a when block: cat-
like still goes to talkToCatWithAmulet/talkToCatPlain (kitten
loneliness reset preserved); dogs (row.dogBreed() != null) go to
talkToDog; everything else stays on the generic talkToPet.
* Fix: dog dialogue always chathead, translation gated on Summoning 14
Drops the b: overhead prefix. Every dog line is now rendered through
the chathead path (npc<Happy>) regardless of life stage.
Dog lines that carry a bark + (translation) pair switch between the
two halves based on the player's Summoning level:
- Below 14 the chathead shows the bark only (player can't understand
their pet yet).
- At 14 or higher the chathead shows the parenthetical translation
only.
Bracketed lines (like "[Throw the stick!]" on the conditional rows)
are dog thoughts the player picks up through context; the brackets
are stripped and the contents render verbatim at any level.
Lines without either form (e.g. "Whurfwhurf whurf...") render
verbatim and don't change with level.
Updated the toml header comments to document the new convention.
* Fix: dog dialogue translation renders on a second chathead line
When the player is at Summoning level 14+, the chathead now shows
the bark on the first line with the parenthetical translation on a
second line beneath it (separated by a newline). NPCDialogue.npc
already splits the text on \n into separate chathead lines, so this
is a one-character format change in the renderer.
Below level 14: bark only, single line. Bracketed thought-lines and
plain lines (no parens) still render verbatim.
* Fix: render dog chathead via the large head component
NPCDialogue.npc picks the chathead component from the npc's
large_head cache flag (head = component 2, small; head_large =
component 1, large). The dog NPCs default to large_head = false, so
their portrait was rendering small. Force largeHead = true on the
dog Talk-to npc<Happy> call so the chathead uses component 1.
* Fix: stop sending the player-face chathead anim on dog dialogue
npc<Happy>(dog.id, ...) was resolving to expression_happy, which is a
human player chathead animation; played on a dog NPC chathead it
renders weird mouth frames or nothing useful. Switch the dog dialog
lines to npc(dog.id, "dog", ...) so AnimationDefinitions falls back
to id -1 (no chathead anim) and the dog portrait renders static
instead of mis-animated. When the actual dog chat-anim ids are
identified in the cache (likely a subset of 2009scape's familiar
chat-anim group 1489, anims 6550..9202) we can add an
expression_dog entry to dialogue_expressions.anims.toml and the
chathead will pick it up automatically.
* Feat: real dog chathead expressions (head-down / normal / no / quiz)
Wired up the four dog chathead animations from the cache:
- expression_dog_down = 6550 (head down, sad)
- expression_dog_normal = 6551 (default idle / talking)
- expression_dog_no = 6552 (head shake)
- expression_dog_quiz = 6553 (questioning)
DogTalk now picks the right mood per line: question marks pick
dog_quiz; lines whose translation starts with "no" pick dog_no;
lines whose bark or translation suggests whining / grumbling /
boredom pick dog_down; everything else stays on dog_normal.
* Switch chat head for dogs back to normal size since correct expression fixed rendering size of chat head.
* Reverted ReaderDouble
* red/blue/green pet eggs now gated at Summoning 99 like black.
* Refactor: drop unused double column type
Pet hunger/growth are stored as ints. The double column readers,
registry entry, and Tables/RowDefinition helpers are dead code now.
* Revert: restore legacy double-decoding in PlayerSave
Kept for save files written before the int conversion landed.
* Refactor: incubator end clock uses epochSeconds base
Wall-clock base survives restarts. The playerSpawn clock-reset block is
no longer needed; only the poll timer is restarted.
* Refactor: single wildcard handler for puppy picker
continueDialogue and interfaceOption now share one
dialogue_pick_a_puppy:* pair; breed resolved via BREEDS.indexOfFirst.
* Refactor: split familiar/pet details:dismiss and :call handlers
Replaces the merged *_details:* wildcards with per-interface handlers
matching upstream style.
* updatePetInterface func opens pet_details (663) for cats, familiar_details (662) for everything else. Cats get their cat-flavoured labels, dogs/dragons get the generic "Call Follower" / "Dismiss" labels from 662.
* Fix: incubator state varbit binary so Take-egg stays visible
The three-valued state varbit (empty / incubating / finished) drove
the cache scenery to a transform whose options array does not include
Take-egg, leaving players unable to collect a hatched egg via the
right-click menu. The
varbit is now binary (empty / incubating); the scenery stays on the
active morph (obj 28359) which carries Take-egg in its cache options,
and "finished" is derived from the expired end-clock at runtime.
A new incubator_finished_announced_* bool guards the hatching message
so the 15-tick poll doesn't spam it. playerSpawn force-rewrites the
state to "incubating" when an egg is present, migrating old saves that
persisted the now-removed "finished" value.
* Style: collapse isFinished to single-expression body
Spotless wants the one-line form.
* feat: add additional pet dialogue
* Fix: ActionQueue.clear(priority) removes every match
ActionList.remove nulls the action's next pointer, so the previous
sweep loop exited after the first removal. Cache next before remove
so all matching actions are dropped in one call.
* Fix: start pet_tick timer synchronously on summon
Previously the hunger/growth timer was started inside a 2-tick
weakQueue alongside the interface open. A strong-priority action
in those two ticks would wipe the weakQueue, leaving the pet with
no timer at all. Start the timer immediately and keep only the
interface open on a normal queue, which survives strong actions.
* Fix: gate cat-amulet drop dialogue behind summon preconditions
dropPet played the catspeak flavour dialogue before validating the
summoning level / existing follower checks, so a player who could
never summon the pet had to sit through three chathead lines just
to receive the failure message. Run a precondition check first and
fall straight through to summonPet's normal failure path when it
fails.
* Perf: index pet rows by item and npc id for O(1) lookup
petRowForItem and petRowForNpc previously did a linear firstOrNull
across the full pets table on every call, and the hot paths (timer
ticks, Interact-with, Talk-to, feed, drop) hit them repeatedly.
Build a Map<String, RowDefinition> for each axis on first access
and reuse it for the rest of the process.
* Fix: skip Custom suspensions whose predicate is unmet on logout
ActionQueue.logout previously force-resumed every active Suspension
type, including Custom whose predicate is what gates the await
condition (e.g. awaitDialogues waiting on dialogue == null). Force
resuming makes the suspended coroutine continue as if the condition
were satisfied, executing dialogue-dependent logic on a player who
is logging out. Only resume Custom when its predicate reports ready
and break out of the fast-forward loop otherwise.
* Fix: snapshot FloorItems queues before iterating
Despawn/Spawn handlers fired inside FloorItems.run() can call
FloorItems.remove/add, which append to the same queue being
iterated -> ConcurrentModificationException crashes the game loop.
Snapshot-and-clear each queue before iterating so re-entrant
entries land on the live queue and get processed next tick,
preserving the existing clear-after-loop semantics.
* Fix: guard kitten chase resolve against rat index reuse
The chase resolves five ticks after the verminc scan. If the rat
dies or despawns in that window, NPCs.indexed at the same slot
might now hold a freshly-spawned NPC that has nothing to do with
this chase. Snapshot the rat's index up front and verify identity
on both the pounce and resolve callbacks before pouncing on / removing
the captured NPC.
* Refactor: summonPet consumes the inventory item atomically
Previously summonPet committed pet/active_item state, then the
caller (dropPet) did inventory.remove afterwards. Anything between
those two commits could leave a player with active_item set but the
inventory item still in hand. Move the remove inside summonPet so
the spawn aborts if the item cannot be consumed, mirroring the
atomic pattern in completePuppySale.
* Fix: don't overwrite incubator state on login when egg is finished
The state varbit only carries the values empty and incubating;
finished is computed from remaining time, never persisted. The
previous playerSpawn unconditionally forced state back to
incubating, justified by a stale comment about an older save
format. Gate the set on isFinished so a finished slot keeps the
state value it logged out with and the comment matches reality.
* Refactor: route pet RNG through the engine's swappable Random
Both Math.random() calls in pet code (kitten chase catch and
timer ambient chatter) used the global JVM random, which can't be
seeded or replaced from tests. Switch to world.gregs.voidps.type.random
to match the rest of the codebase and let tests inject a deterministic
Random via setRandom.
* Style: clear() pet_active_item instead of setting empty string
PetState.clearPetStats already uses clear() for all the per-pet
counters; pickupPet and dismissPet were inconsistently using
set("pet_active_item", "") to represent the same idea. Switch
both to clear() so deactivation flows match a single convention.
* Fix: log and notify the player when a refund itself fails
completePuppySale and spiritShards both refund the player when the
follow-up add fails, but if that refund also fails (inventory state
shifted between the original check and the rollback) the player
silently loses coins or shards. Log a warning with the account name
and surface a "contact staff" message so the loss is recoverable
out-of-band.
* Docs: spell out pet_details_stats bit-packing in sendPetDetailsStats
The (growth shl 1) or (hunger shl 9) layout matches a specific
client-side varp; the magic shifts were silently fragile to anyone
reading the function in isolation. Name the varp, point to the
TOML that documents it and explain why bits 0 and 8 are unused.
* Docs: explain why Pet registers both Drop and Release handlers
Investigation: ItemDestroy registers itemOption(\"Release\") as a
catch-all wildcard that opens a destroy-confirm dialog. The cache
wires pet items to \"Release\" (not the default \"Drop\"), so the
pet-specific registration takes precedence and routes the click
straight into dropPet instead of the destroy confirm. Drop is kept
for the handful of pet items that still expose it. Document the
non-obvious dispatch interplay so a future cleanup doesn't trim
either registration as dead code.
* Refactor: convert cat catspeak dialogue from recursion to a loop
talkToCatWithAmulet and talkToHellcatWithAmulet previously re-called
themselves at the end of every option body, so a long catspeak
conversation grew an unbounded coroutine stack. Replace the
self-call with a while-loop that exits when the player picks the
quit option. Same player-visible behaviour, flatter control flow.
* Fix: reset hunger and warn when a pet metamorphoses
A pet growing into its next stage kept the hunger value and warn
counter from its previous stage. If a kitten reached the starving
threshold and immediately metamorphosed to grown, the warn level
stayed maxed out, so the new stage's hungry / starving messages
could never re-fire when their thresholds were crossed again.
Clear both vars on metamorphose so the new stage gets a fresh
warning ladder. Growth is already zeroed by the caller; loneliness
only applies to kittens so it stops naturally.
* feat: add Shoo away option for clockwork cat
Register a direct Shoo away npcOperate handler for
pet_clockwork_cat_baby. Opens an "Are you sure?" confirm and runs
dismissPet on yes with clockwork-flavoured "Whir..." / "winds down
and stops" lines so the toy is released (destroyed) rather than
returned to inventory.
* Fix: match clockwork cat's actual Shoo opcode
The cache exposes the option as "Shoo", not "Shoo away", so the
previous registration never matched any menu click. Rename the
npcOperate string to match.
* Style: tighten clockwork shoo dialogue
Drop the "Shoo!" / "Whir..." overhead lines and simplify the
confirm prompt to "Are you sure you want to release your pet?"
with Yes / No answers.
* feat: show "NA" for growth percentage on fully-grown pets
Once a pet has reached its final stage, the X% growth label on
iface pet_details (663) is meaningless. Register iface component
17 as growth_percentage and override its text with "NA" via
interfaces.sendText whenever sendPetDetailsStats runs for a final-
stage pet. The packed varp bar is sent at 100% so the underlying
visual stays consistent with the textual sentinel.
* Fix: defer pet growth NA override by a tick to outrun CS2 refresh
Component 17 on iface pet_details has a stateChange CS2 handler
(script 820) bound to varp 1175 that re-formats the growth text
as "X%" whenever the varp updates. Sending interfaces.sendText in
the same tick as the varp send gets clobbered by the CS2, so the
label kept showing 100%. Defer the "NA" text override by one tick
via queue so it lands after the CS2 has finished its refresh.
* Fix: use varbit sentinel 101 to render NA for pet growth
Reverse-engineered CS2 (script 753 on iface 662, same pattern on
script 820 / iface 663) checks growth and hunger varbits for the
literal value 101 and renders "NA" instead of "X%" when it sees
the sentinel. Send 101 in the growth bits of the packed pet_details_stats
varp when the pet is at its final stage and let the client format
the label. Drops the deferred-sendText workaround and the
growth_percentage component registration; both are now redundant.
* Fix: remove feed_phrase overhead chatter from pet feeding
2011 RuneScape pets did not shout an overhead phrase when fed —
feeding only emitted a chat message like "Your pet happily eats
the raw shark." Drop the row.stringOrNull("feed_phrase")?.say
call in PetFeeding and clear the now-dead feed_phrase entries
from pets.tables.toml (broav, penguin, giant_crab, vulture rows)
plus the schema declaration. Ambient idle_phrases and hungry_phrase
overhead text remain — those were authentic.
* Fix: drop player chathead echo on clockwork cat shoo choices
The Yes / No options used typed option<Quiz> / option<Sad>, which
makes choice replay the option text as a player chathead line. The
clockwork cat shoo should be a silent confirm — switch to untyped
option so picking Yes / No goes straight to the action without the
player saying the word back.
* Refactor: gate puppy purchase on inventory.isFull up front
The owner's "Where are you going to put it, on your head?" line
already lived in completePuppySale, but by that point the player
had already picked a breed and sat through the price haggle. Mirror
the existing check at the top of puppyTree (right after the
alreadyHasDog gate) so a player without inventory space gets the
brush-off immediately, before the breed picker even opens. The
late check in completePuppySale stays as a defensive guard against
state shifts during the multi-tick dialogue.
* Fix: split parenthese translation onto its own line in pet chathead
talkToPet passed npc: lines verbatim, so data like
"Honk! (Hello!)" rendered on a single chathead line with the
translation jammed against the bark. Insert a newline before the
opening paren so the npc() splitter routes bark to line1 and the
translation to line2. Mirrors what DogTalk's renderDogLine already
does for dogs, applied here to every non-dog pet that uses the
parenthese translation convention.
* Fix: word-wrap dialogue text after splitting on \n
The npc / player chathead helpers used to skip the font-width
wrap whenever the input contained \n, on the assumption that the
caller had already laid out the lines. That was fine until pet
talk started inserting \n before the parenthese translation —
suddenly long translations overflowed the chathead because they
no longer ran through splitLines. Wrap each chunk after splitting
on \n so callers get both their hard breaks and per-chunk
word wrapping. The chunked-into-4-line-chathead fallback handles
any case where wrapping pushes the total over the 4-line limit.
* Fix: Adjust word wrapping width
* Revert "Fix: snapshot FloorItems queues before iterating"
This reverts commit 912542082abe19c814c609d4fd796d95faf97a19.
* feat: add Soul Wars Slayer pets and Dungeoneering sneakerpeeper
* feat: secondary skill gate for pets + skill_below condition
summonPet now optionally runs a second has(skill, level) check
from row "secondary_skill" / "secondary_level" so sneakerpeeper
can require Dungeoneering 80 AND Summoning 80 — its authentic
double gate. The secondary check stays dormant for every other
pet because they leave both columns unset.
matchesPetCondition gains a "skill_below:<skill>:<level>" branch
that fires when the player's level in the named skill is strictly
under the threshold. Wires up the adult sneakerpeeper "Summoning
< 91" gibberish line in upcoming pet_talks data.
Schema gains the two optional columns and sneakerpeeper's row
sets them to summoning / 80.
* feat: add Talk-to dialogue for Soul Wars Slayer pets + sneakerpeeper
Adds row skeletons for the six pets shipped in db30ed60e so Talk-to
fires real conversations instead of the ambient idle_phrases
fallback. Each pet has at least one chathead row and one overhead
row; abyssal_minion has an unconditional whip-request row plus two
conditional rows that fire only when an abyssal whip is carried;
adult sneakerpeeper has a low-Summoning conditional row using the
new "skill_below:summoning:91" condition syntax.
Conversation skeletons are short by design and can be expanded with
fuller dialogue trees from the RuneScape Wiki transcripts at
https://runescape.wiki/w/Transcript:<Pet_name>.
* Test: realign chat wrap expectations with 400-px width
NPCChatTest and PlayerChatTest's "Long line wraps" cases were
written against the 380-px font-split width. The dialogue helpers
now use 400 px, so an extra word ("wrapped") fits on the first
line. Update the expected line1 / line2 split point to match.
* tools: add wiki transcript importer for pet dialogue
ImportPetTranscript fetches a Transcript:<Page_name> page from
runescape.wiki, parses the wikitext into pet_talks-style rows and
prints them to stdout. The user runs it and appends the output to
data/skill/summoning/pet/pet_talks.tables.toml themselves.
Heuristics:
- Top-level == Heading == sections become row groups.
- Sub-headings containing "overhead" or "random" mark overhead rows.
- "removed" / "hunger" / "starv" / "fed" sub-sections are skipped.
- "spawn"/"baby" or "adult"/"grown" in heading sets the stage field.
- "If ..." or comparison-bearing headings emit a # TODO condition hint
for hand-editing.
- Wiki markup (links, bold, italic, html) is stripped from line bodies.
* data: replace abyssal_minion placeholder rows with full conversations
* data: move pet overhead bubbles into ambient idle_phrases pool
The five overhead-only pet_talks rows (creeping_hand_2, minitrice_2,
baby_basilisk_2, baby_kurask_2, abyssal_minion_overhead) were being
picked as Talk-to results, which is the wrong channel for overhead
bubble text. Dogs and cats let those phrases surface periodically
via the 30-second ambient roll instead, so do the same here: fold
each pet's overhead lines into its idle_phrases list in
pets.tables.toml and delete the now-redundant pet_talks rows. The
ambientChatter helper at PetTimers.kt picks one at random per tick
with the existing 12% probability.
Talk-to now picks only conversational chathead rows, matching the
behaviour of every other pet.
* data: full baby basilisk dialogue + tiered hunger phrases
Replaces the single baby_basilisk_1 placeholder row with three
conversations the user supplied:
- baby_basilisk_eyes — "Why do my eyes sting?" / kill-with-eyes reveal.
- baby_basilisk_origin — serpent egg + chicken-or-egg exchange.
- baby_basilisk_mum — "Mum!" mistaken-identity loop.
Replaces the placeholder hungry_phrase with the tiered eye-burning
hungry_phrases list (hungry / starving / runaway), so the
tickHunger threshold crossings now use the proper escalating
overhead bark instead of the generic "Hisss...".
* data: full creeping hand dialogue
Replaces the single creeping_hand_1 placeholder row with three
rows reflecting the conversations the user supplied:
- creeping_hand_valiant — "hand it to you" / "hand-some" / "no wrist
for the wicked" closer.
- creeping_hand_shake — handshake / "Gimme five" / out-of-hand.
- creeping_hand_truth — "hand-le the truth" puns.
Overhead lines were folded into idle_phrases in 61f36d041 already.
* data: full minitrice dialogue + tiered hunger phrases
Replaces the single minitrice_1 placeholder row with three
conversations:
- minitrice_feathered_frog — "I minitrice" / "feathered frog" reveal.
- minitrice_deaded — the long "you deaded me" stare-prank arc.
- minitrice_dinner — "Me kill you" / hooman dinner closer.
Replaces the generic "Squawk!" hungry_phrase with the tiered
eye-burning hungry_phrases list (hungry / starving / runaway),
matching the escalation we use for baby_basilisk.
* data: full baby kurask dialogue
Replaces the single baby_kurask_1 placeholder with three rows for
the conversations the user supplied:
- baby_kurask_grunts — grunt-decoding game (pain / hungry / sleepy).
- baby_kurask_economy — economy + Ice Mountain "fascinating response".
- baby_kurask_ispy — I-spy with knave / knucklehead / kumquat guesses.
Overhead lines already live in idle_phrases from 61f36d041.
* data: full sneakerpeeper dialogue (baby + grown)
Replaces the four sneakerpeeper placeholder rows with the eight
conversations the user supplied across the two stages:
Baby (sneakerpeeper_spawn):
- sneakerpeeper_baby_collection — Player-skin-hat horror collection.
- sneakerpeeper_baby_floating — staring-contest-to-the-death message.
- sneakerpeeper_baby_origin — "Player-lips" creepy crush.
- sneakerpeeper_baby_songs — folk-rhyme + earmuffs threat. One song
line paraphrased in-character rather than quoted verbatim.
Grown (sneakerpeeper):
- sneakerpeeper_grown_spawning — eyeball-squeezing spawning request.
- sneakerpeeper_grown_hairball — Thok's-eyelashes confession.
- sneakerpeeper_grown_hivemind — Lakhrahnaz / Haasghenahk ice puns.
- sneakerpeeper_grown_eyemax — one-eye depth-perception / monocles.
The low-Summoning conditional row from be5a9455c stays — adult
sneakerpeeper still emits the garbled line when Summoning < 91.
* data: add dialogue for sneakerpeeper
* Fix: per-pet chathead animation override for sneakerpeeper
Pets whose own NPC id has no entry in the client CS2 enum
pet_details_chathead_animations_normal (1276) fall back to the
generic defaultInt (8373), which is the wrong expression for
sneakerpeeper. The enum DOES contain entries for the in-dungeon
Sneakerpeeper NPC (cache id 22 -> anim 8463 normal / 8464 sad),
but our pet variants (13089 baby, 13090 grown) aren't keyed.
Adds two optional row columns:
- chathead_npc -> NPC string id whose cache enum entry the client
CS2 should look up for the pet details panel chathead anim. The
varbit follower_details_chathead_animation now writes this when
set, otherwise the pet's own id as before.
- chathead_anim -> animation string id resolved server-side for
chat dialogue chathead. Falls through to the enum lookup keyed
by pet NPC id, then to enum default.
Sneakerpeeper's row sets chathead_npc = "sneakerpeeper_chathead"
(alias for cache NPC 22) and chathead_anim =
"expression_sneakerpeeper_normal" (alias for anim 8463). The two
anim aliases and the npc alias are added to pet.anims.toml /
pet.npcs.toml. Every other pet keeps its existing fallback path.
* Refactor: move sneakerpeeper chathead anims into dialogue_expressions
pet.anims.toml is for pet interaction animations (stroke, pounce
etc.). NPC chathead expressions live in dialogue_expressions.anims.toml
alongside expression_dog_*, expression_cat_*, expression_tree_*.
Move expression_sneakerpeeper_normal / _sad to where they belong;
no code changes needed since the lookup is by string id.
* Fix: disable sneakerpeeper chathead anim override until right value known
The chathead_npc / chathead_anim guesses from c623be2ad picked anim
8463/8464 based on enum 1276 key 22, which turns out not to be the
correct animation for the pet sneakerpeeper variants. Until the
right value is identified, opt out of the override entirely.
Adds a chathead_disabled boolean row column. When true:
- updatePetInterface skips the follower_details_chathead_animation
varbit set, leaving it at whatever the previous summon wrote (or
default).
- talkToPet sends -1 to the chathead anim component, which the
client treats as "clear / no animation".
Sneakerpeeper's row sets chathead_disabled = true and drops the
two override columns; the alias entries in pet.npcs.toml and
dialogue_expressions.anims.toml stay for future re-use once the
correct anim is identified.
* feat: substitute "Player" with display name in pet dialogue + -1 chathead anim
Two follow-ups for sneakerpeeper:
1. Wiki-sourced rows use the literal stand-in "Player" wherever
the in-game line should show the player's display name (e.g.
"Player find Sneak's collection, yes?"). Add a whole-word
regex substitution in talkToPet that replaces "Player" with
player.name on every line. Whole-word match so "Player-skin"
/ "Player-lips" / "Player," still substitute correctly without
touching unrelated substrings.
2. Make chathead_disabled write -1 to the
follower_details_chathead_animation varbit instead of skipping
the set. Clear intent: explicitly request "no anim" on the
panel until the correct value is identified, rather than
inheriting whatever the previous summon wrote.
* Fix: send chathead anim -1 directly to iface component, bypass varbit
Writing -1 to follower_details_chathead_animation (varbit 4282)
didn't take effect: the underlying bit storage clamps the value
to a positive range, so the CS2 enum lookup still landed on the
generic defaultInt instead of "no anim".
Switch to a direct sendAnimation override on the chathead model
component, which exists on iface 662 (familiar_details, id 1) and
iface 663 (pet_details, id 3). Both are now named "chathead" in
summoning.ifaces.toml. When chathead_disabled is set we skip the
varbit path entirely (so CS2 doesn't fire a refresh) and push -1
straight onto the component instead.
* chore: remove outdated comments.
* feat: add ::expr_scan admin command for chathead anim discovery
The existing ::expr command sets a single chathead animation on
dialogue_npc_chat1 — useful when you already know the anim id you
want to view. For pets / NPCs without a named expression_* alias
we need to walk through a numeric anim range and identify the
right one visually.
::expr_scan <npc-id> <start-anim> <end-anim>
Opens the dialogue chathead, sets the given NPC's head model, and
cycles every anim id in [start..end]. Each step shows the current
anim id in the chathead title and waits on click-to-continue, so
the player can advance at their own pace and read off the id when
they see the right animation.
Mirrors ::expr's iface / packet shape so the chathead UX is
identical.
* Spotless apply fix formatting on fun substitutePlayerName.
* Fix: open expr_scan dialogue once, update per iteration instead of reopen
The previous loop called open("dialogue_npc_chat1") on every
iteration, which (via Player.open's same-type close behaviour)
closed-and-reopened the dialogue iface between every anim step.
That packet churn appears to be what was crashing clients during
::expr_scan runs.
Open the iface once before entering the loop, then per-iteration
just push the head / anim / text / lines and pauseButton. Guard
the loop with an interfaces.contains check so a manual close of
the dialogue ends the scan cleanly with an informational message,
and wrap the body in try/finally so the iface always closes on
exit (range complete, early return, or exception).
* Fix: switch expr_scan from pauseButton to auto-advance tick delay
Manual click-to-continue cycling kept crashing the client. The
continue button click triggers client-side iface teardown on the
same tick our coroutine resumes; the next loop iteration was then
firing npcDialogueHead + sendAnimation packets into an iface the
client was already winding down, which appears to have killed the
connection.
Replace pauseButton with a delay(tickDelay) (default 3 ticks,
~1.8s) so the dialogue stays open continuously and we just push
fresh anim + title every cycle. Send the head model once outside
the loop since it's stable across iterations. The 4th arg
(tick-delay) is optional so callers can slow the scan down for
finer inspection.
* feat: wire sneakerpeeper chathead anims to 8008 / 8009
Identified via ::expr_scan: anim 8008 is the sneakerpeeper normal
expression, 8009 is the excited variant.
- Update the expression_sneakerpeeper_normal alias to 8008.
- Rename expression_sneakerpeeper_sad to _excited and set to 8009.
- Sneakerpeeper row drops chathead_disabled and sets chathead_anim
= "expression_sneakerpeeper_normal".
- updatePetInterface gains a chathead_anim branch: when set, push
the resolved anim id directly onto the chathead component via
sendAnimation, bypassing the varbit / CS2 lookup entirely. Same
approach the talkToPet path already uses.
- Drop the dead sneakerpeeper_chathead NPC alias — chathead_npc is
no longer needed for sneakerpeeper now that chathead_anim covers
it.
* Chore: found the expression animation for sneakerpeeper and sneakerpeeper spawn.
* Fix sneakerpeeper chathead default-animation flash
* Fix sneakerpeeper chathead default-animation flash
* Fix: Undo chathead animation enum replacement of -1 values
* Feat: Add monkeyspeak amulet dialogue for pet monkey
* Fix: Declare pet_index as transient player var
Player.pet uses get/set("pet_index", -1) but no variable was
declared anywhere in data/. Without a declaration the slot is
non-transient and post-relog summonPet(restart = true) could read
a stale index that NPCs.indexed resolves to an unrelated NPC.
Mark persist = false so the slot is cleared on login; the
playerSpawn handler in PetScripts re-spawns the NPC and writes
the fresh index.
* Fix: Roll back consumed item if pet NPC spawn throws
summonPet removed the inventory item, then called NPCs.add. If the
add threw (unknown id, NPC slot cap, etc.) the item was destroyed
with no pet to show for it. Wrap the spawn in try/catch and re-add
the item before rethrowing so a failed summon is reversible.
* Fix: Guard Player.pet getter against NPC index reuse
NPCs.indexed(pet_index) returned whatever NPC currently occupied
that slot. If the pet despawned and the slot was recycled (a fresh
NPC spawned into the same index in the same tick), every
`pet?.index == target.index` check downstream resolved against an
unrelated NPC.
Reject the lookup when pet_index is unset, when pet_active_item is
blank, or when the resolved NPC's id isn't registered to a pet row.
* Fix: canSummonPet honours row-driven gating skill
The precondition hardcoded Skill.Summoning while summonPet itself
reads row.skillOrNull("skill"). Pets whose primary gate is Slayer
or Dungeoneering (sneakerpeeper, Soul Wars pets) would pass the
precondition incorrectly when the Summoning level happens to match
but the actual gating skill is too low, or fail it incorrectly when
Summoning is too low but the row's real skill check would pass.
Read the same row skill as summonPet so the two checks agree.
* Fix: Always release stroke movement freeze in finally
stroke() set cat.movement_delay = Int.MAX_VALUE and only cleared it
inside the post-dialogue `pet?.index == cat.index` guard. If the
pet was dismissed, shooed, or logged out during the chathead
sequence the cat NPC kept the freeze for the rest of its life.
Move cat.stop("movement_delay") into a finally block around the
dialogue so the freeze is released on every exit path.
* Fix: Validate and cap expr_scan input range
scanExpressions called args[0..2].toInt() with no try/catch and no
upper bound on (end - start). A typo such as `expr_scan foo 0 999999`
threw NumberFormatException or tied up the player's action queue
for hours iterating through every cache animation.
Switch to toIntOrNull with a clear error message and cap the scan
range at 1024 (about 10 minutes at the default 3-tick delay).
* Fix: Limit 400px chathead wrap to pet dialogue overload
The PR widened chathead word-wrap from 380 -> 400 inside the shared
splitDialogueLines helper used by every npc(npcId, expression, ...)
and player(expression, ...) call. That re-flowed line breaks for
every existing dialogue in the game even though the change was only
needed for the new pet chathead Int-anim overload.
Restore the original 380 single-pass behaviour on the string-
expression npc/player paths and move the 400px + hard-break-aware
splitter into a new splitPetDialogueLines used only by the Int-anim
overload. Revert NPCChatTest / PlayerChatTest expectations to the
pre-PR wrap.
* Fix: Align PetExclusivityTest setup with stricter pet getter
Follow-up to "Guard Player.pet getter against NPC index reuse".
The new getter requires pet_active_item to be set before
NPCs.indexed(pet_index) is considered a real pet. The summon-
familiar exclusivity test assigned player.pet directly without
the matching active_item write, so the guard correctly returned
null and the test no longer represented an active pet.
Mirror the real-life summonPet contract by writing the active
item alongside the slot.
* Revert "Fix: Limit 400px chathead wrap to pet dialogue overload"
This reverts commit eace2cb942d768149dfd94d5479cdb2d5b2f0b8c.
* Revert "Fix: Always release stroke movement freeze in finally"
This reverts commit 5bef40653233ff8a3987980a21f9d9abafdc3ede.
* Feat: Add Summoning pets
* Feat: Add dragon egg drops to drop tables
* Feat: Add kitten interactions.
* Feat: Add kitten interactions.Pet + incubator data now load via the standard .tables.toml infrastructure
* Feat: Address pet PR review feedback
- Drop IncubatorDefinitions; walk Tables/Rows for incubator eggs directly.
- Rename incubator state varbits to list-format with "empty/incubating/finished" labels; per-region end timer now a tick-based clock via start/remaining.
- Collapse pet item/npc handlers to single comma-separated ops; thin PetDefinitions to a lazy Tables wrapper.
- Replace the pet_stats blob with per-pet hunger/growth/warn vars; add DoubleValues so 0.0 defaults prune correctly.
- Open pet_details (663) instead of familiar_details for pet followers; dismiss handler now globs *_details.
- Stop the kitten in place when the Stroke option is used.
* Fix: route iface 663 Call/Dismiss by component, not option label
The pet_details (663) call/dismiss buttons emit cache option labels
distinct from familiar_details (662) ("Call Follower" / "Dismiss
Familiar" / "Dismiss Now"). The existing handlers keyed off those
exact strings and silently no-op'd on pets.
Wildcard the option label (`*:*_details:call` / `dismiss`) and branch
on follower-vs-pet inside. Pet dismiss always calls pickupPet so the
item is returned to inventory; familiar dismiss keeps the
confirm-vs-immediate distinction.
* Fix: pet Dismiss releases the pet instead of returning the item
Both pet_details:dismiss and the summoning_orb's right-click Dismiss
now call dismissPet, which removes the NPC and clears pet state
without putting the item back in inventory — the pet runs free. The
pet_details path wraps it in an "Are you sure you want to release your
pet?" confirmation since the item is gone for good; the orb's
right-click path stays a one-click action (matches the familiar
"Dismiss Now" semantics).
* Fix: pet kitten Stroke - cancel queued player walk
If the player had a minimap walk queued when they selected Stroke, the
player kept moving during the dialogue suspension while the kitten sat
in EmptyMode. When Follow was restored at the end of the interaction
the kitten had to sprint several tiles to catch up.
Clear the player's pending steps and face the kitten at the start of
the interaction so the player stays put for the animation.
* Fix: migrate pet code to weakQueue + fix Chase vermin
PR #980 deleted softQueue; replace the three pet/kitten call sites
with weakQueue (closest semantic match: easily-preempted fire-and-
forget) and drop the leftover `player.` prefixes inside the lambdas
since Player is now the lambda receiver.
While in KittenInteract, fix two bugs in chaseVermin:
- The cat used to chase pirates because "pirate" contains the
substring "rat". Predicate now matches the npc string id with a
`_` word boundary (rat / *_rat / rat_* / *_rat_*).
- The cat never caught anything because the 5-tick callback always
fired the failure message. Roll a 33% catch chance up front; on
success despawn the rat and message the win. Adjacency guard
covers the case where the rat wanders before the kitten arrives.
* Feat: chase-vermin dialogue + wiki-flavoured catch messages
Pre-chase dialogue runs before the kitten engages: player asks if it
wants to hunt, kitten replies, player tells it to take it easy. On
success the wiki "Hey well done puss, you got it!" / kitten
"MeeeoooooW!" pair fires, and every 10th catch logs the milestone
"Well done puss! N horrible rodents caught!". Caught count persists
on the player via pet_rats_caught.
* Feat: wiki cat dialogue, catspeak amulet variants
Branches every cat interaction on whether the player is wearing a
catspeak amulet and whether the pet is a kitten or adult cat.
Without amulet (kitten + adult cat):
- Stroke narrates "You softly stroke your cat." + "Purr...purr..." +
"The cat turns on its side while you bend down to pet it."
- Chase vermin keeps the existing 3-line pre-chase chatter and the
wiki success/fail/milestone copy.
- Shoo away gains the wiki "Are you sure you want to shoo away the
cat?" confirmation + "You choose not to shoo away the cat." cancel
message.
- Talk-to plays the simple "Hey puss! Any news?" / Purr / Meow lines.
- "There aren't any vermin around." message when no rat in scan radius.
With catspeak amulet (adult cat only):
- Stroke prepends Player "Who's a good cat then?" / Cat "Me, me.
Scratch me behind the ears." chathead exchange.
- Chase vermin uses the wiki adult opener "Go on get that nasty
rodent." / Cat "Yesss, food."
- Talk-to opens a 4-option chathead loop (how are you / how old /
where to / what to do) with a quit option.
- Pick-up plays "Come here furball." / Cat "Can we go adventuring
together again, soon?" / Player "Soon, I promise." before the item
returns to the inventory.
- Drop/Release plays "Hey cat, do you fancy stretching your legs..."
/ Cat "Miaaow, Are we going adventuring?" / "We'll see puss, we'll
see." before summoning.
KittenInteract registers Interact-with against every cat-like npc
(baby, grown, overgrown) so the menu is reachable past kittenhood.
PetScripts in Pet.kt routes the Pick-up / Drop / Release / Talk-to
operate handlers to the catspeak variants when the conditions match,
falling back to the generic pet handlers otherwise.
* Fix: move chaseVermin conversational lines to chathead
Three full-sentence lines were being emitted as overhead chat when
they should have been chathead dialogue:
- Cat's pre-chase reply "Meoowww. Yeah! Let's go kick some fur!"
was cat.say (overhead bubble); now an npc<Happy> chathead line.
- Player's success "Hey well done puss, you got it!" and the every-
10th-catch "Well done puss! N horrible rodents caught!" were
player overhead; now player<Happy> chathead.
Short imperative / sound-effect lines (Go on puss..., Shoo cat!,
Meeeoooooowwww!, Eek!, MeeeoooooW!) stay as overhead — those belong
above the speaker's head.
* Feat: extend cat drop dialogue to kittens + add no-amulet meow
- Catspeak "Hey cat, do you fancy stretching your legs..." exchange
now plays for every cat life stage (including kittens), not only
the adult/overgrown items.
- Dropping a cat without the amulet now plays a "Miaow!" overhead
one tick after the spawn settles (the new NPC is wired in
summonPet's own weakQueue at +2; we fire at +3 so pet?.say
targets the freshly summoned cat).
Folded the duplicated Drop/Release item-option bodies into a single
suspend helper Player.dropPet to keep the branching in one place.
* Feat: kitten pounce animation when catching the rat
Splits the chase-vermin weakQueue into a +4 pounce tick and a +5
resolve tick. When the cat has reached the rat (adjacency check) it
plays the new pet_pounce_kitten anim, then a tick later restores
Follow and resolves the catch/miss outcome.
The 9168 anim id is a best guess on the kitten anim grouping (stroke
is 9173) and may need swapping for one of 9167/9169-9172 after a
visual check in-game.
* Fix: correct kitten pounce anim id (9163, not 9168)
* Fix: scope legacy double->int variable migration to IntValues keys
PR #799 added a Double->Int*10 conversion in PlayerSave's variables
load branch to migrate old fractional XP saves into the new int
experience format. The check was unconditional, so every Double
variable got collapsed on load — including pet_*_hunger and
pet_*_growth (declared format = "double"), which were turning into
ints after every restart and then defaulting back to 0.0 on the
next get<Double>() because Int can't be smart-cast to Double.
Gate the conversion on VariableDefinitions.get(key)?.values being
IntValues so it only fires for keys the current schema actually
expects to be ints. Doubles for genuinely-double-typed vars now
round-trip verbatim, which lets pet hunger / growth persist across
server restarts.
* Feat: kitten faces the rat before pouncing
* Fix: kitten doesn't wander after Chase vermin gets interrupted
The chase resolve was wrapped in nested weakQueues, which the new
ActionQueue clears every time any Strong action enters the queue
(clicking another NPC/object, taking a hit, teleporting). When the
resolve dropped, the cat was left in EmptyMode and reverted to its
default idle/hunt wander.
Swap the two weakQueue calls in chaseVermin for queue (Normal
priority). Normal queues sit in the main queue list, only weakQueue
gets cleared on Strong, so the Follow restoration always fires —
even after a mid-chase interruption.
* Feat: second round of PR #983 review fixes
Merges upstream/main (PR #984 brings inc(max) and skill drop gating).
Engine reverts per Greg:
- Delete DoubleValues from VariableValues, plus its factory mapping.
- Remove both legacy double migrations from PlayerSave (variables loop
and the experience fractional path). Doubles are not used in any
player variable in the new schema.
Pet stats moved to integers on a 0..10000 scale:
- pet_state.vars.toml regenerated with format = "int".
- pets.tables.toml swaps growth_rate (double) for growth_per_tick
(int = rate * 50 * 100 per 30s tick).
- PetState drops the PetStats class and updatePetStats wrapper; the
getters return Int, callers use inc(key, amount, max = PET_STAT_MAX)
and dec(key, amount) directly (PR #984 added the max parameter).
- PetTimers thresholds become 7500 / 9000 / 10000 on the new scale,
HUNGER_BABY/GROWN become 125 / 90 per tick. PetFeeding feeds 1500.
- sendPetDetailsStats divides by 100 on the way out so the orb bars
stay on the 0..100 client scale.
PetDefinitions deleted; data lives in Tables now. The PetDefinition
data class is gone too. Pets.kt adds RowDefinition extensions
(isCatLike, stageForItem/Npc, npcFor/itemFor, nextStageItem/Npc,
isFinalStage, ambientPhrases) plus petRowForItem / petRowForNpc and
allPetRows lookup helpers. Every caller (Pet, KittenInteract,
PetTimers, PetFeeding) now consumes RowDefinition directly. The
Koin singleton in GameModules is gone.
Incubator suffix derivation collapses to
it.target.id.removePrefix("incubator_"). Renamed the per-region base
objects to incubator_taverley (28550) and incubator_yanille (28352);
kept the shared incubator_idle (28336) / incubator_active (28359)
transform names so the dispatch finds a registered string id for the
displayed mesh. target.id always reflects the base, so the suffix
extraction never sees idle/active. The regionVarbits map and the
tile.region.id helper are deleted.
KittenInteract:
- isRat simplifies to id.startsWith("rat") so giant_rat / warped_rat
drop out of the chase pool.
- talkToCatWithAmulet replaces the while(true) + keepGoing flag with
Greg's recursive-option pattern: each answer recurses back into
talkToCatWithAmulet, the quit option has no body.
pet.npcs.toml: every options = { ... } map deleted; npcs only need the
id field.
dragon.drops.toml: black dragon egg gated on skill = "summoning",
equals = 99 (PR #984 syntax).
Tests adapted: PetLogoutTest stats are ints; IncubatorUseEggTest uses
incubator_taverley for the test fixtures.
* Fix: cat stays put after Stroke interaction
The previous version swapped the cat into EmptyMode for the duration
of the dialogue and restored Follow afterwards. Restoring Follow
recalculates against player.steps.follow immediately, which queues a
step toward the player's last footprint tile and the cat ends up
walking a tile out then back in.
Use the movement_delay clock to suspend the cat's movement for the
length of the dialogue instead, then clear the clock plus any queued
steps at the end. Follow mode stays active throughout, so when the
clock releases there is nothing for the recalculation to fight and
the cat stays where it was being stroked.
* Feat: Talk-to dialogue for the Yanille / Taverley pet shop owners
Both pet shop owners (npc ids 6892 and 6893) share the same dialogue
tree per the wiki, so a single PetShopOwner script handles both with
one npcOperate matcher.
Main menu offers four options: open the pet shop (pet_shop), buy a
puppy, ask about other available pets, and sell spirit shards.
Puppy purchase guards against owning a dog already, runs the wiki
exchange about the 500 gold price, then either takes the coins and
hands over a default colour puppy or backs out gracefully on no
inventory space or insufficient coins. Six breeds are wired
(Bulldog / Dalmatian / Greyhound / Labrador / Sheepdog / Terrier).
The available-pets branch reproduces the wiki tree verbatim covering
nuts, birds + incubator, Karamja lizards, geckos / raccoons and the
banana-in-the-trap monkey tip.
Spirit shard sale uses intEntry for the count, drops the shards and
credits 25 coins each, with a graceful early-out when the player has
none on them.
Chathead expressions picked per line: Quiz for the player asking
questions, Neutr…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rather than doubles, deprecate storing any doubles to avoid precision errors