Summoning: complete familiar abilities (specials, passives, dialogue)#1052
Conversation
Port talk dialogue and special-op interactions for ~62 summoning familiars from a reference implementation, matching the existing per-familiar Script pattern (SpiritWolf/AbyssalLurker/AbyssalParasite). - Per-familiar Interact dialogue scripts (random + context-aware: inventory/equipment/stat/BoB-slot/run-energy/staged-state checks), grouped via wildcard ids for minotaurs, cockatrices, titan tiers, and gorajo/clay dungeoneering familiars. - Special ops: unicorn Cure (gated; inert until poison exists), pyrelord logs->firemaking, lava titan Lava Maze teleport, dreadfowl/compost mound Boost Farming. - Generic expression_familiar_* chathead animation family + unicorn cure anim/gfx; dialogue="familiar" + interacts=false on all scriptable familiars.
The generic expression_familiar_* family applied the shared cat head talk animations to every familiar's chathead model, which animates incorrectly for non-feline familiars. Drop the dialogue="familiar" override and the generic family so each familiar renders its own chathead model with no forced (wrong) animation. Correct per-familiar talk anims can be added later as expression_<familiar>_* families (via ::expr_scan), like the existing wolf/bird/leech sets.
Familiars are creatures; add large_head = true to every familiar npc def so their dialogue renders the bigger chathead (as cows, camels and other creature npcs already do).
Foragers (starting with Magpie) periodically gather loot from a forage_<familiar> drop table into the familiar's inventory while summoned, and the player retrieves it via the familiar's Withdraw/Take option (reusing the beast-of-burden inventory + interface). Players can't deposit their own items into a forager. - Forager script: 30s forage timer rolling the drop table into the familiar inventory, plus Withdraw/Take open handlers. Foragers are detected data-drivenly by the existence of a forage_<id> drop table (no new npc-def param, which must map to a real cache param). - forage_magpie drop table (gems/rings) in summoning.drops.toml. - Magpie marked as a beast-of-burden (cap 30) to reuse the storage + withdraw UI; store() rejects deposits into foragers. - Start/stop the forage timer on summon/dismiss. Other forager familiars are now just data: add a forage_<name> table.
Port the forage drop tables for beaver, spirit spider, granite crab, granite lobster, ibis, macaw (grimy herbs), compost mound, evil turnip, fruit bat, giant ent, stranger plant, void ravager and the cockatrice family, and mark each as a beast of burden (cap 30) so they reuse the forage inventory + Withdraw/Take interaction. Tables model the reference's "1/outOf chance, then drop one" via a 1/<roll> outer roll deferring to an `_items` sub-table for multi-item foragers (also corrects magpie, which previously dropped every tick). Item ids resolved to void string ids. Dungeoneering hoardstalker foragers (tier t1-t10) are deferred.
The farming boost was wrongly offered as a "Boost Farming" entry in a
Talk-to/Boost choice on Interact. Make Interact talk-only again and
trigger the boost from the familiar's special-move ("Special") option
instead, via the cast_dreadfowl_strike / cast_generate_compost buttons
on the summoning orb and familiar details tab.
The boost is on the familiar's "Special" right-click option, not the
special-move interface button. Use npcOperate("Special", ...) guarded
to the player's own familiar.
Clicking the dreadfowl's "Special" option now plays the asking-to-fight dialogue, the dreadfowl's special animation (7810) + gfx (1523, from darkanrs' Dreadfowl Strike sync), then boosts Farming by one.
Player plays gfx 1307 and the dreadfowl plays gfx 1317 alongside its special-strike animation (5387) when boosting Farming via the Special option.
The dreadfowl/compost-mound Special Farming boost now refuses to apply when Farming is already boosted (e.g. by a garden pie), and is stripped when the familiar is dismissed or dies (both route through dismissFamiliar) - only if it's still the active, unchanged boost.
Introduce a data-driven registry of the passive skill-level boosts a summoned familiar grants (beaver +2 Woodcutting, granite lobster +4 Fishing, obsidian golem +7 Mining, etc.) and a Player.familiarBoost function. The boost is read live from the active follower and added to the effective level in skill success checks - so it's invisible (not in the skill tab), never lets the player act above their real level, and disappears the moment the familiar is dismissed or killed. Wired into woodcutting, fishing, mining, firemaking and thieving. Hunter boosts (arctic bear, wolpertinger, kyatt, larupia) are in the registry but inert until the Hunter skill is implemented.
Summoned familiars now periodically shout a random overhead line every ~30s (driven by the existing familiar timer), matching RS. Lines are a data-driven map keyed by familiar id, sourced from the RuneScape Wiki transcript "Overhead dialogue" sections - 83 familiars covered. Phoenix and the gorajo (bloodrager/stormbringer/etc.) tiers have no overhead on the wiki and stay silent.
Replace the hardcoded Kotlin FAMILIAR_OVERHEAD map with a data-driven familiar_overhead table (list<string> "lines" column, keyed by familiar id), matching how the rest of the project stores config (e.g. the pet talk tables). The familiar timer reads it via Rows/Tables, so lines can be edited as data without touching code.
Familiars now play their own "fade into existence" animation (alongside the existing summon graphic) when summoned and when called. The per-familiar spawn anims are ported from darkanrs' Pouch spawnAnim values, mapped by npc id, as named <familiar>_spawn animation defs.
Split the grouped IronSteelTitan stub (a single grunt) into separate IronTitan and SteelTitan scripts with their full random talk dialogue, ported from the RuneScape Wiki transcripts (iron titan: 4 conversations; steel titan: 5).
darkanrs' albino rat spawn anim (16080) is outside void's animation cache. The albino rat reuses the generic rat skeleton (Base 1152) for its render anims; that skeleton's summoning-era animation is 8545 - matching how arctic bear (8522, bear skeleton) and the bats (8279, bat skeleton) take their spawn anim from their skeleton's list. 8545 is in cache range, so all 144 familiars now have a spawn animation.
albino rat spawn anim (16080) is outside 634 cache animation boundry. The albino rat reuses the generic rat skeleton (Base 1152) for its render anims; that skeleton's summoning-era animation is 8545 - matching how arctic bear (8522, bear skeleton) and the bats (8279, bat skeleton) take their spawn anim from their skeleton's list. 8545 is in cache range, so all 144 familiars now have a spawn animation.
…dialogues' into feat/summoning/interactions-and-dialogues
A player and its familiar count as one side, so they may both attack the same target in single-way combat: - Target.attackable exempts the owner<->familiar pair from the single-combat gate. - FamiliarCombat lets the familiar be sent at an NPC outside multi-combat when it's the owner's own target (offensive/defensive assist). Combat familiars now also grant their owner combat xp for the damage they deal - the owner's chosen melee style skill (or the familiar's range/magic type) plus Constitution - mirroring CombatExperience.
Familiars wouldn't approach a target they were sent at - they stood frozen and only attacked if it was already in range. Two causes: - Movement only ran pathfinding for players; NPCs used cheap single-step line movement that can't route around obstacles. Owned familiars now pathfind like players so they actually walk to the target. - CombatMovement applied the spawn/aggro leash to familiars, so a target beyond their ~9-tile aggro range (e.g. one commanded up to 16 tiles away) dropped them straight to EmptyMode. Owned familiars are now exempt from the leash - they chase what their owner directs and fall back to following when the fight ends.
Locks in the Target.attackable exemption: in a single-way zone (one attacker per target) the owner and its familiar count as one side, so the familiar may still join the owner's fight.
Reverts the single-way assist allowance: combat familiars can only attack in multi-combat zones. In single-way combat the player can still use the familiar (storage, foraging, special moves) but it won't assist in the fight. Removes the owner/familiar single-combat exemption from Target.attackable and the single-way assist path in FamiliarCombat.
Player and familiar are separate attackers under the single-way "one attacker per target" rule: - commandFamiliarAttack: drop the multi-combat-only gate so a familiar can be ordered to attack in single-way, but pre-check Target.attackable so it can't pile onto an NPC already under attack (e.g. one the owner is fighting), rejecting with a message instead. - assistFamiliar: auto-join only in multi-combat; in single-way the owner's target is theirs alone, so the familiar must be ordered at a separate NPC. Tests cover: ordered solo attack in single-way, no sharing from the player's side, no auto-assist in single-way, auto-assist in multi.
Covers the approach path (interact -> combat) for a target beyond the familiar's approach range, the scenario that wasn't exercised by the adjacent-target ordered-attack test.
When ordered/assisting, the familiar walked to its target via the interact-then-combat path. The interact mode gives up the instant a player or npc fully blocks the route (cantReach -> EmptyMode) and never re-paths once the obstruction clears, leaving the familiar frozen. Drive the familiar with CombatMovement directly instead: it re-paths every tick and (for npcs) never bails on cantReach, so the familiar routes around or waits out blockers and resumes as soon as they move.
NPCCharacterTargetStrategy.destination() returns Tile.EMPTY for npcs with an EMPTY walk mode. Familiars have that walk mode (they don't wander), so destination() always returned EMPTY and recalculate() short-circuited (EMPTY == cleared destination) - the familiar never re-pathed, following only its initial path. A stationary target worked; a target that moved/retreated left the familiar stuck at the old tile. Exempt owned familiars (owner_index set) from the stationary-npc short-circuit so they recompute a path to the target's current tile each tick and chase it.
Familiars persist in CombatMovement and never call cantReach, so an unreachable target (fled somewhere with no path) left them stuck chasing forever. Track ticks of no progress while still far from the target; if the familiar makes none for a short grace period it gives up and reverts to EmptyMode, which NPCTask turns back into following its owner. Moving, or being all but in range (just waiting on a free attack tile in a crowd), resets the grace period so genuine fights aren't abandoned.
When a familiar dealt the killing blow it was the npc's killer, but the death handler only drops loot (and grants slayer / logs the kill) when the killer is a Player, so familiar kills dropped nothing. Resolve a familiar killer to its owner so the loot drops for the player.
The varbit map's zeros are unverified gaps from the original capture, so familiars like the desert wyrm and arctic bear resolved no enum entry and bobbed with humanoid expression animations on non-human head models. Give them a static head (-1) instead, matching what the details interface shows, until their real values are identified.
The details interface animates value-0 familiars (desert wyrm, arctic bear) correctly because the client's datamap lookup returns the enum default - a generic head-bob - for missing keys. Match that instead of sending a static head; the earlier crash was the typographic punctuation, not this animation.
talkToPet keyed the chathead enum by npc id - the wrong key space, the enums key on the varbit value - and passed the resulting animation name as an expression, which double-prefixed the lookup and rendered a static head. Mapped pets (sneakerpeeper) now resolve through familiarChatheadAnimation like familiars; unmapped pets fall back to the neutral expression.
Summoning's current level doubles as the points pool, so gates using Level.has - summoning a pouch familiar, dropping a pet, incubating an egg, enchanting headgear - wrongly refused players whose points had drained below the requirement. Switch them to hasMax, which reads the level derived from experience. Boostable-skill gates (bunyip's Cooking, pyrelord's Firemaking) are left on the current level.
|
GregHib
left a comment
There was a problem hiding this comment.
A few comments and fixes but overall looking really strong
Much neater, good recommendation. Co-authored-by: Greg <GregHib@users.noreply.github.com>
Opening report abuse in select-player mode (cs2 246) unhooks the chat input key listener (137:55) and sets report mode varc 11. The client's close script (cs2 675) never restores either, so chat stays dead until relog. The restore script (cs2 244) has no client-side callers - the server is expected to run it. Close the interface server-side on submit and run cs2 244 whenever report_abuse closes. Closes GregHib#1061
* Start on pitfall traps * Update pitfall messages * Fix bank withdraw full inventory message happening when all items were removed * Add pitfall level checks * Add pitfall collapse * Add pitfall logic and tests * Remove hunter class * Fix teasing, add tests for larupia and kyatts * Remove duplicate mazchna * Fix barbarian course advanced laps reward collection * Remove unused hunter data
* Fix run orb status invalid on first login * Fix logging in requiring display name instead of account name * Fix being able to create an account using a display name that's already in use * Allow spaces in message request usernames fixes GregHib#1068 * Some script params * Fix account unit test * Fix account merge tests * Fix account definitions incorrectly using account name instead of display name as the key
* Add random event system and maze event Implements the random event trigger, kidnap teleport and mysterious old man framework, plus the Maze random event where players navigate to a central shrine before time runs out. Tutorial Island is tagged no_random_events so events don't fire there. Ref GregHib/void-private#223 * Add in-place random event framework Extend the random event system beyond the kidnap-to-instance pattern so in-place nagging events (Certer, Sandwich Lady, Evil Twin) can be built: - RandomEvents.noteAndTeleport: ignore penalty that notes the player's inventory then exiles them, so an event can't be macroed through. - InPlaceRandomEvent + startInPlaceEvent/endInPlaceEvent: spawn an event NPC beside the player that follows and nags on a timer, applying the ignore penalty when its lifetime runs out. - Decouple entry from the old man: the trigger now calls RandomEvents.start directly and each launcher owns its intro. Kidnap events call the new mysteriousOldMan() helper; in-place events spawn their own NPC. Maze's launcher allocates its instance before the intro so a resumed timer doesn't fail the event mid-intro. - rewardCostumeOrCoins: shared collect-the-set reward (camo, lederhosen). Ref GregHib/void-private#223 * Add Freaky Forester random event The player is teleported to the forester's clearing and told to kill a pheasant with a specific number of tails. Only the assigned pheasant drops the correct raw pheasant; wrong ones drop an incorrect one that the forester rejects. Handing in the correct pheasant rewards the first missing lederhosen piece, or 500 coins if the set is complete. - Conditional pheasant drops keyed to the killer's assigned task - Blocks attacking further pheasants once one is killed - Static pheasant spawns in the clearing; forester dialogue drives the task - Removes the unconditional pheasant drop table (drops are now per-task) Ref GregHib/void-private#255 * Drop the raw pheasant to the floor instead of the inventory The assigned pheasant now drops the raw pheasant as a normal killer-owned floor drop (non-tradeable, private) for the player to pick up and hand to the forester, matching the live behaviour. * Add Sandwich Lady random event First in-place event on the framework: the Sandwich Lady appears beside the player pushing her tray and nags until spoken to. Talking opens her refreshment tray (interface 297); choosing the food she offered hands it over and ends the event in place, while any other choice knocks the player out via the note-and-teleport ignore penalty. Talking mid-combat hands over the correct food directly, and other players are turned away. Adds RandomEvents.completeInPlace for events that finish without a return teleport, and defines interface 297's seven food buttons (10-22). Ref GregHib/void-private#261 * Sandwich Lady reacts to the tray choice She now says "Hope that fills you up!" when handing over the correct food, and smacks the player for 3 before the knockout when they pick the wrong one, matching live behaviour. * Give the Sandwich Lady knockout its animation and dialogue - Show "Hope that fills you up!" as a chatbox line on a correct pick so the player actually sees it, instead of a fleeting overhead force-chat. - On a wrong pick she swings her baguette (animation), smacks the player, who plays the death animation and fades out before waking up elsewhere. * Add Drill Demon random event Sergeant Damien whisks the player to his exercise yard and barks an order. Each of the four mats shows an exercise on its sign (a varbit); the player uses the mat matching the order, performing the exercise animation. Four correct exercises earns a piece of the camouflage outfit (helmet/top/bottoms, first missing) or 500 coins if the set is complete. - Shuffles the four exercises across the mat signs each round - Exercise index 0 (jog) is read with a 0 default since the variable store clears vars set to their default value - Reuses the shared kidnap/complete and rewardCostumeOrCoins helpers Ref GregHib/void-private#254 * Fix Drill Demon signs and mat positioning - Encode the four exercises as sign varbit values 1-4 (0 = no sign). The variable store clears any var set to its default 0, so a sign set to 0 never reached the client - one mat showed no sign and the client's signs drifted out of sync with the server, making the correct mat read as wrong. - Walk the player onto the mat before performing the exercise. * Polish Drill Demon exercise and sign-change effects - On using a mat the player now faces the sergeant, plays the exercise sound and waits two ticks before performing the exercise animation. - When the signs reshuffle, a puff of smoke (gfx 2000) plays on each sign tile in turn from sign 1 to sign 4, a tick apart, as it changes. * Play the sign-change puff on the sign tile (mat y + 2) * Sweep the Drill Demon sign puffs half a tick apart (Option B) The four signs now all change on one tick and the smoke puffs sweep across them client-side, sign 1 to sign 4, half a tick (15 client ticks) apart, instead of one server tick between each change. * Use the large chathead for Sergeant Damien's dialogue * Show the sign item dialogue after Drill Demon's order After Sergeant Damien names the exercise, an item box shows the matching sign with "Go to this mat and <action>!" ("this mat" in red), matching 2009scape. Adds the missing push-up sign item (10946). Darkan doesn't implement Drill Demon. * Make Drill Demon wait before the first order On arrival the yard shows "Follow Sergeant Damien's orders!" and waits about five seconds before the sergeant barks the first exercise. Using a mat (or talking to him) before the order is given has him snap "I haven't given you the order yet, worm!" and earns no credit for that attempt. * Add Certer random event One of the certificate brothers appears beside the player, bows and nags. Talking opens the identify interface (184) showing an item and three descriptions; picking the one that matches the item rolls the shared gem/coin reward table, a wrong pick gives nothing, and ignoring him applies the note-and-teleport penalty. Adds the shared random_event_certer loot table and its roller (reused later by Pillory). Ref GregHib/void-private#253 * Rework Certer to use the item box and a choice menu Interface 184 isn't the Certer window in the 634 cache (only five components, and the runtime item definition can't supply a model to render on it), so the puzzle didn't display. Show the item with the standard item dialogue box and pick the description from a choice menu instead - reliable and still faithful to the identify-the-item mechanic. Certer isn't implemented in Darkan (only 2009scape). * Use interface 184 for Certer with the real 634 layout Decoded interface 184's full component types: it has a text prompt (comp 1) and three item-model options (comps 4-6, not text), so the 634 version reads out a description and the player picks the matching item model - the reverse of the older cache. The certer now sends the description to the prompt and three item models to the options. * Render Certer option items with sendModel The type-6 model option components don't render items sent via INTERFACE_ITEM, so send each item's model id directly. The runtime item definition doesn't expose model ids, so the eleven pool items' models (decoded from the cache) are listed alongside their descriptions. * Fix Certer interface: item model in the box, text on the options The item to identify is a separate model component (comp 0, the box) and the three A/B/C Select components hold the description text. Send the item's model to the box and the three descriptions (one correct, two false) to the options; the player picks the description matching the shown item. * Drive Certer's interface 184 via its varbits Interface 184 builds itself from a load script (cs2 2136): it reads the three option descriptions from enum 2224 and the item model from enum 2225, all selected by varbits (5855-5863). So instead of pushing text and models (which the script overwrites), the event now sets those varbits - three description keys, the model key for the answer ((desc+2)%9 in enum 2225), and rotation/scale - then opens the interface. The dynamically created option rows declare their Select action so clicks validate. Removes the temporary interface-click debug log. * Point Certer's option clicks at the right components The load script's clickable option rows are components 4-6, not 0-2, so map option_1/2/3 to them; clicks now register and the matching answer rewards. * Add Mime random event The player is whisked to a stage where the mime performs emotes to copy. A per-player mime spawns in an instance and repeats the target emote on a timer; the event hooks the emote tab (alongside its own handler) to check the copy. Four correct emotes in a row unlock the mime emotes (glass wall/box, climb rope, lean) and award a mime costume piece, or coins if the set is complete. No 2009scape/Darkan reference - built from the wiki. Ref GregHib/void-private#258 * Teleport the mime event player to 2008, 4762 * Add Evil Bob random event An evil incarnation of Bob the cat teleports the player to the Scape2009 island and demands fish. A terrified servant pans the camera to the fishing spot Bob likes; the player nets there (the fish comes out already cooked), uncooks it at the cold fire, and serves the raw fish to Bob. The right fish sends him to sleep and opens the exit portal for a 650 Fishing or Magic XP reward; a wrong one makes Bob more attentive so an extra correct fish is needed. Ported from 2009scape (no Darkan version). Ref GregHib/void-private#262 * Add Evil Bob random event An evil incarnation of Bob the cat teleports the player to the ScapeRune island and demands fish. A terrified servant pans the camera to the fishing spot Bob likes; the player nets there (the fish comes out already cooked), uncooks it at the cold fire, and serves the raw fish to Bob. The right fish sends him to sleep and opens the exit portal for a 650 Fishing or Magic XP reward; a wrong one makes Bob more attentive so an extra correct fish is needed. Ref GregHib/void-private#262 * Face Evil Bob and pause before the raspberry send-off * Face Evil Bob and pause before the raspberry send-off * Fix Evil Bob's spawn posiiton. * Use the cat chathead expressions for Evil Bob's dialogue * Add Quiz Master random event Odd One Out on interface 191: pick the golden model that doesn't belong; four correct wins a choice of 1000 coins or a random item. Ported from 2009scape. Ref GregHib/void-private#260 * Make the Quiz Master answer models clickable Interface 191's models are non-clickable in the cache, so send the access mask on open; without it the interface has no way to answer. * Wire the Quiz Master answers to the interface's real buttons The clickable answer buttons are the invisible components 3-5 (setting=1 in the cache), not the display models on 6-8. Target those instead of trying to unlock the non-clickable model components. * Handle Quiz Master answers as dialogue continues Interface 191's setting=1 buttons deliver clicks as dialogue-continue events (like choice() options), not interface options. Suspend each question on pauseInt and resume it from a continueDialogue handler keyed to the button clicked. * Add Pillory random event A guard arrests the player into a pillory; unlock it (interface 189) by picking the swinging key matching the spinning lock, N-in-a-row (starts 3, +1 and resets on a wrong pick, cap 6). Escaping rewards a roll of the shared certer table. Ported from 2009scape; object/tile ids remapped for the 634 cache (cage 777, city pillory tiles). Ref GregHib/void-private#268 * Add Pillory random event A guard arrests the player into a pillory; unlock it (interface 189) by picking the swinging key matching the spinning lock, N-in-a-row (starts 3, +1 and resets on a wrong pick, cap 6). Escaping rewards a roll of the shared certer table. Ported from 2009scape; object/tile ids remapped for the 634 cache (cage 777, city pillory tiles). Ref GregHib/void-private#268 * Add Surprise Exam random event The Mysterious Old Man teleports the player into Mr Mordaut's classroom; talking to Mordaut opens a 'what comes next?' pattern quiz (interface 103) - pick the item that completes the three shown. Three correct passes the exam and Mordaut names one of four coloured doors; leaving through it returns the player with a Book of Knowledge (an XP lamp). Ported from 2009scape. Ref GregHib/void-private#263 Also relax the flaky Pillory arrest assertion to allow landing beside a cage when the stocks block the tile. * Handle Surprise Exam answers as dialogue continues Interface 103 opens in the chatbox, so answer clicks arrive as dialogue-continue events (like Quiz Master's 191), not interface options. Open it as a dialogue_box, suspend each question on pauseInt, and resume from a continueDialogue handler keyed to the option clicked. * Open the Surprise Exam quiz in the main game window Interface 103 is a centred main-window modal, not a chatbox dialogue; set type=main_screen and take answers as interface options again. * Send the access mask for the Surprise Exam answer buttons Interface 103's answer icons are enabled in the cache but the client won't report a click until the server sends the interface settings; unlock them on open. * Unlock Surprise Exam answer buttons over their item slot Send the interface settings for slot 0..0 of each answer component so the client enables the click. * Dispatch clicks on the Surprise Exam item icons getInterfaceItem tried to resolve the clicked answer icon against an inventory the display-only interface doesn't have, so the click was silently dropped. Allowlist surprise_exam_pattern like the other display-only item interfaces (farming_equipment_store, common_item_costs). * Dispatch clicks on the Surprise Exam item icons getInterfaceItem tried to resolve the clicked answer icon against an inventory the display-only interface doesn't have, so the click was silently dropped. Allowlist surprise_exam_pattern like the other display-only item interfaces (farming_equipment_store, common_item_costs). * Use the large chathead for Mr Mordaut's dialogue * Address review: clear random events on reload, reuse addOrDrop Make RandomEvents AutoCloseable and register it in ContentLoader so a script reload resets the launcher map instead of keeping stale entries. Replace the duplicated give-or-drop helpers with the existing Player.addOrDrop. * Rework the Mime event around interface 188 and the theatre The old man drops the player into the theatre (2008,4764), challenges them, then they walk to the watch spot. The Mime performs one of eight emotes under the spotlight, bows to the player, and the player copies it by picking the matching emote on interface 188 (dialogue_macro_mime_emotes). Correct picks say 'Correct!', pause ~8s, and after three the player is teleported home with the mime emotes unlocked and a costume piece. Spotlight objects (3644) are wired but their on/off animation is left as a TODO pending the anim ids. * Run the Mime event in the real theatre instead of an instance Teleport to the actual region 8010 coordinates and use the statically-spawned Mime rather than copying the region into an instance. * Show Mime prompts as chatbox dialogue Display 'Watch the Mime...' as a no-prompt chatbox line during each performance, and make 'Correct!' a chatbox statement instead of a game message. * Drop the Mime 'Correct!' text down a line Show it on the lower line of a two-line message box (empty first line), since the box top-aligns its already-centred text. * Bump the Mime 'Watch the Mime' prompt down a line too Generalise the empty-first-line message helper and use it for both the watch prompt and 'Correct!'. * Animate the Mime spotlights on and off Wire object 3644's turn-on (1136) and turn-off (1135) animations into the spotlight helper: the Mime's light comes on while it performs, the player's while they copy. * Let the Mime spotlight finish before the emote The spotlight on/off animation runs ~3 ticks; wait that out after lighting the Mime (before it performs) and after lighting the player (before they pick and copy). * Teleport the Mime player out with the modern teleport Play the modern teleport animation and gfx, wind up, then return the player and land with the modern landing gfx. * Use the modern teleport helper for the Mime exit Replace the hand-rolled teleport animation with teleport(origin, "modern") after clearing the event state. * Add Kiss the Frog random event The Frog Herald hops up and whisks the player to the Land of the Frogs, where one of the identical frogs wears a crown - the Frog Prince (female player) or Princess (male). Kissing the crowned frog restores it to human form and gives a gift box; talking to a plain frog turns the player into a frog until they apologise to the crowned one. Ported from 2009scape (content/global/random/event/frog_princess); ids/anims mapped to the 634 cache. Ref GregHib/void-private#256 * Fix frog chatheads and face-before-kiss in Kiss the Frog Give the frog NPCs the 'old' dialogue namespace (2009scape used FaceAnim.OLD_*) so their chatheads animate, and pause after turning to face each other before the kiss. * Rework Kiss the Frog fail/escape flow and fix royal detection - Crowned royal is now frog_9_frogland (3300); the rogue decorative 3300 spawn that looked crowned but gave plain dialogue is now a plain frog. - Talking to wrong frogs offends the royal; a third offence morphs the player into a plain frog and banishes them to a frog cave, where a second crowned royal releases them (random exile, no reward). - Broaden the plain-frog handler to frog_*frogland so the bare frogland frogs baked into the region respond instead of "Nothing interesting". - Restrict the Land of the Frogs to the six social tabs; land arrival moved to 2445,4770 with crown-spawn tiles near it. - Make the kiss and cave-escape sequences walk-off safe: a walkTrigger fades the player out and completes/escapes so they can't be stranded mid-dialogue. * Seat the player during the Quiz Master show Play the sit animation (2378) when the contestant takes their seat in the studio, and stand back up on winning before teleporting home. * Add Pinball random event * Add Prison Pete random event * Add Lost and Found random event * Add Cap'n Arnav's Chest random event * Add Beekeeper random event * Add Evil Twin random event * Add Gravedigger random event * Reward all random events with a random event gift * Add Mystery box random event reward * Add Casket treasure * Add Iffie's costume store * Remove dead code and unnecessary comments from random events * Exile without noting items on sandwich lady knockout * Allow players to disable random events with ::randomevents * Skip already-noted items in the random event ignore penalty * Guard the maze shrine and reset the chest cap between mazes * Only unlock Prison Pete's locks when keys are handed over * Exile the player when the beekeeper's hive build fails * Let the Quiz Master resume an interrupted quiz * Clean up Kiss the Frog royals on logout and resume on relog * Resume random event progress on relog and clean up leftover state * Add Pinball random event tests * Organise random event NPC definitions into per-event configs * Fix the unreachable home teleport cooldown handler * Move random event settings to game.properties * Reuse String.an for MysteryBox.kt * Add random event exception areas * Include morphing rings/transforms in random event eligibility * Trigger random events on a timer instead of experience drop Increase cooldown between 1-2 hours Add config for lost and found event chance * Disable random events in integration tests to avoid issues * Fix random event trigger tests --------- Co-authored-by: GregHib <greghib@users.noreply.github.com>
…rees The projectile protocol carries no rotation (darkan's packet is field-identical - its 'angle' byte is our 'curve'), but the cache's graphic definitions do: opcode 6 is a model rotation in degrees (the revision's existing values are only ever 90/180/270, gfx 1402's was 0). Adds a GraphicEncoder mirroring the decoder, and a FixGraphics tool (fixGraphics gradle task, also wired into CacheBuilder) that patches gfx 1402 with a 180 degree rotation after round-trip-validating the re-encode - so Toad Bark keeps its smoke-trailed cannonball, flying the right way round. The local cache has been patched.
killer.getValue("owner_index") doesn't exist; use the indexed get with a
default like every other owner_index read.
…ne fallbacks Revert the ItemOnNPCInteract base-id fallback combinations; transformed id lookups are intentional. Item-on-familiar handlers now register with a "<id>*" prefix so the wilderness "<id>_combat" form's keys are expanded at registration instead.
Replace the manual transform branching with def(source) for player sources, which already resolves the transform_id attribute. NPC sources keep a minimal owner_index guard: base familiar forms deliberately lack the Attack option but combat() re-validates attackable() every tick, so retaliation against familiars would otherwise disengage.
Replace the hardcoded HEADGEAR list with the enchanted_headgear table in enchanted_headgear.tables.toml; the script reads rows for registration and lookups.
Replace the hardcoded boost map with the familiar_boosts table in familiar_boosts.tables.toml (flat amount plus percentage of base level).
FamiliarSpecialMoves now implements AutoCloseable, cleared by Script.clear like the other content registries via ContentLoader.
…ialogue api Revert the familiar branch in the private npc renderer; the dialogue target overload resolves familiarChatheadAnimation and passes it via npc(npcId, expression, text), as does the pet talk script. sendChat falls back to raw animation names/ids for such custom expressions.
Drop the cache graphic rotation patch (FixGraphics/GraphicEncoder); the dedicated toad bark projectile (1402) faces backwards in the cache, so shoot with gfx 1401 and the hand cannon's cannonball (2143) instead.
…mbat Sends the real InteractInterfaceNPC instruction for familiar_details cast_famine at an npc; the locust engages while the player gains no combat mode or target.
The pyrelord's fire breath (8085) doesn't fit the forge regent's rig; it breathes with its own animation (7871, also its Inferno breath).
Combat only emits combatStop for fights whose first swing landed, so a familiar engaged mid-retaliation whose target died pre-swing kept the stale target - a titan's called special would re-engage the same npc after it respawned. Death now clears every attacker's matching target.
…m:HarleyGilpin/void into feat/summoning/interactions-and-dialogues
…GregHib#1052) * Add familiar interactions and dialogues for summoning Port talk dialogue and special-op interactions for ~62 summoning familiars from a reference implementation, matching the existing per-familiar Script pattern (SpiritWolf/AbyssalLurker/AbyssalParasite). - Per-familiar Interact dialogue scripts (random + context-aware: inventory/equipment/stat/BoB-slot/run-energy/staged-state checks), grouped via wildcard ids for minotaurs, cockatrices, titan tiers, and gorajo/clay dungeoneering familiars. - Special ops: unicorn Cure (gated; inert until poison exists), pyrelord logs->firemaking, lava titan Lava Maze teleport, dreadfowl/compost mound Boost Farming. - Generic expression_familiar_* chathead animation family + unicorn cure anim/gfx; dialogue="familiar" + interacts=false on all scriptable familiars. * Use static familiar chatheads instead of generic cat anims The generic expression_familiar_* family applied the shared cat head talk animations to every familiar's chathead model, which animates incorrectly for non-feline familiars. Drop the dialogue="familiar" override and the generic family so each familiar renders its own chathead model with no forced (wrong) animation. Correct per-familiar talk anims can be added later as expression_<familiar>_* families (via ::expr_scan), like the existing wolf/bird/leech sets. * Use large chatheads for summoning familiars Familiars are creatures; add large_head = true to every familiar npc def so their dialogue renders the bigger chathead (as cows, camels and other creature npcs already do). * Add forager familiar system with Magpie Withdraw Foragers (starting with Magpie) periodically gather loot from a forage_<familiar> drop table into the familiar's inventory while summoned, and the player retrieves it via the familiar's Withdraw/Take option (reusing the beast-of-burden inventory + interface). Players can't deposit their own items into a forager. - Forager script: 30s forage timer rolling the drop table into the familiar inventory, plus Withdraw/Take open handlers. Foragers are detected data-drivenly by the existence of a forage_<id> drop table (no new npc-def param, which must map to a real cache param). - forage_magpie drop table (gems/rings) in summoning.drops.toml. - Magpie marked as a beast-of-burden (cap 30) to reuse the storage + withdraw UI; store() rejects deposits into foragers. - Start/stop the forage timer on summon/dismiss. Other forager familiars are now just data: add a forage_<name> table. * Add foraging to the remaining overworld forager familiars Port the forage drop tables for beaver, spirit spider, granite crab, granite lobster, ibis, macaw (grimy herbs), compost mound, evil turnip, fruit bat, giant ent, stranger plant, void ravager and the cockatrice family, and mark each as a beast of burden (cap 30) so they reuse the forage inventory + Withdraw/Take interaction. Tables model the reference's "1/outOf chance, then drop one" via a 1/<roll> outer roll deferring to an `_items` sub-table for multi-item foragers (also corrects magpie, which previously dropped every tick). Item ids resolved to void string ids. Dungeoneering hoardstalker foragers (tier t1-t10) are deferred. * Move dreadfowl/compost farming boost to the special-move option The farming boost was wrongly offered as a "Boost Farming" entry in a Talk-to/Boost choice on Interact. Make Interact talk-only again and trigger the boost from the familiar's special-move ("Special") option instead, via the cast_dreadfowl_strike / cast_generate_compost buttons on the summoning orb and familiar details tab. * Trigger dreadfowl/compost farming boost from the Special npc option The boost is on the familiar's "Special" right-click option, not the special-move interface button. Use npcOperate("Special", ...) guarded to the player's own familiar. * Add dreadfowl Special dialogue and animation Clicking the dreadfowl's "Special" option now plays the asking-to-fight dialogue, the dreadfowl's special animation (7810) + gfx (1523, from darkanrs' Dreadfowl Strike sync), then boosts Farming by one. * Use dreadfowl special animation (5387) * Add dreadfowl farming-boost graphics Player plays gfx 1307 and the dreadfowl plays gfx 1317 alongside its special-strike animation (5387) when boosting Farming via the Special option. * Make familiar Farming boost non-stacking and removed on dismiss The dreadfowl/compost-mound Special Farming boost now refuses to apply when Farming is already boosted (e.g. by a garden pie), and is stripped when the familiar is dismissed or dies (both route through dismissFamiliar) - only if it's still the active, unchanged boost. * Add passive invisible familiar skill boosts Introduce a data-driven registry of the passive skill-level boosts a summoned familiar grants (beaver +2 Woodcutting, granite lobster +4 Fishing, obsidian golem +7 Mining, etc.) and a Player.familiarBoost function. The boost is read live from the active follower and added to the effective level in skill success checks - so it's invisible (not in the skill tab), never lets the player act above their real level, and disappears the moment the familiar is dismissed or killed. Wired into woodcutting, fishing, mining, firemaking and thieving. Hunter boosts (arctic bear, wolpertinger, kyatt, larupia) are in the registry but inert until the Hunter skill is implemented. * Add random familiar overhead dialogue Summoned familiars now periodically shout a random overhead line every ~30s (driven by the existing familiar timer), matching RS. Lines are a data-driven map keyed by familiar id, sourced from the RuneScape Wiki transcript "Overhead dialogue" sections - 83 familiars covered. Phoenix and the gorajo (bloodrager/stormbringer/etc.) tiers have no overhead on the wiki and stay silent. * Move familiar overhead dialogue into a table definition Replace the hardcoded Kotlin FAMILIAR_OVERHEAD map with a data-driven familiar_overhead table (list<string> "lines" column, keyed by familiar id), matching how the rest of the project stores config (e.g. the pet talk tables). The familiar timer reads it via Rows/Tables, so lines can be edited as data without touching code. * Play per-familiar spawn animation on summon and call Familiars now play their own "fade into existence" animation (alongside the existing summon graphic) when summoned and when called. The per-familiar spawn anims are ported from darkanrs' Pouch spawnAnim values, mapped by npc id, as named <familiar>_spawn animation defs. * Add iron and steel titan talk dialogue Split the grouped IronSteelTitan stub (a single grunt) into separate IronTitan and SteelTitan scripts with their full random talk dialogue, ported from the RuneScape Wiki transcripts (iron titan: 4 conversations; steel titan: 5). * Add albino rat spawn animation (best-guess 8545) darkanrs' albino rat spawn anim (16080) is outside void's animation cache. The albino rat reuses the generic rat skeleton (Base 1152) for its render anims; that skeleton's summoning-era animation is 8545 - matching how arctic bear (8522, bear skeleton) and the bats (8279, bat skeleton) take their spawn anim from their skeleton's list. 8545 is in cache range, so all 144 familiars now have a spawn animation. * Add albino rat spawn animation (best-guess 8545) albino rat spawn anim (16080) is outside 634 cache animation boundry. The albino rat reuses the generic rat skeleton (Base 1152) for its render anims; that skeleton's summoning-era animation is 8545 - matching how arctic bear (8522, bear skeleton) and the bats (8279, bat skeleton) take their spawn anim from their skeleton's list. 8545 is in cache range, so all 144 familiars now have a spawn animation. * Remove incorrect Albino rat familiar spawn animation. * Let familiars assist in single-way combat and grant owner xp A player and its familiar count as one side, so they may both attack the same target in single-way combat: - Target.attackable exempts the owner<->familiar pair from the single-combat gate. - FamiliarCombat lets the familiar be sent at an NPC outside multi-combat when it's the owner's own target (offensive/defensive assist). Combat familiars now also grant their owner combat xp for the damage they deal - the owner's chosen melee style skill (or the familiar's range/magic type) plus Constitution - mirroring CombatExperience. * Make familiars path to and chase their combat target Familiars wouldn't approach a target they were sent at - they stood frozen and only attacked if it was already in range. Two causes: - Movement only ran pathfinding for players; NPCs used cheap single-step line movement that can't route around obstacles. Owned familiars now pathfind like players so they actually walk to the target. - CombatMovement applied the spawn/aggro leash to familiars, so a target beyond their ~9-tile aggro range (e.g. one commanded up to 16 tiles away) dropped them straight to EmptyMode. Owned familiars are now exempt from the leash - they chase what their owner directs and fall back to following when the fight ends. * Test familiar assists owner in single-way combat Locks in the Target.attackable exemption: in a single-way zone (one attacker per target) the owner and its familiar count as one side, so the familiar may still join the owner's fight. * Familiars only fight in multi-combat, not single-way Reverts the single-way assist allowance: combat familiars can only attack in multi-combat zones. In single-way combat the player can still use the familiar (storage, foraging, special moves) but it won't assist in the fight. Removes the owner/familiar single-combat exemption from Target.attackable and the single-way assist path in FamiliarCombat. * Familiars fight solo in single-way, can't share a target Player and familiar are separate attackers under the single-way "one attacker per target" rule: - commandFamiliarAttack: drop the multi-combat-only gate so a familiar can be ordered to attack in single-way, but pre-check Target.attackable so it can't pile onto an NPC already under attack (e.g. one the owner is fighting), rejecting with a message instead. - assistFamiliar: auto-join only in multi-combat; in single-way the owner's target is theirs alone, so the familiar must be ordered at a separate NPC. Tests cover: ordered solo attack in single-way, no sharing from the player's side, no auto-assist in single-way, auto-assist in multi. * Test familiar walks to a distant ordered target in single-way Covers the approach path (interact -> combat) for a target beyond the familiar's approach range, the scenario that wasn't exercised by the adjacent-target ordered-attack test. * Keep familiar pursuing its target when its path is blocked When ordered/assisting, the familiar walked to its target via the interact-then-combat path. The interact mode gives up the instant a player or npc fully blocks the route (cantReach -> EmptyMode) and never re-paths once the obstruction clears, leaving the familiar frozen. Drive the familiar with CombatMovement directly instead: it re-paths every tick and (for npcs) never bails on cantReach, so the familiar routes around or waits out blockers and resumes as soon as they move. * Let familiars chase a moving combat target NPCCharacterTargetStrategy.destination() returns Tile.EMPTY for npcs with an EMPTY walk mode. Familiars have that walk mode (they don't wander), so destination() always returned EMPTY and recalculate() short-circuited (EMPTY == cleared destination) - the familiar never re-pathed, following only its initial path. A stationary target worked; a target that moved/retreated left the familiar stuck at the old tile. Exempt owned familiars (owner_index set) from the stationary-npc short-circuit so they recompute a path to the target's current tile each tick and chase it. * Familiar returns to following its owner when it can't reach its target Familiars persist in CombatMovement and never call cantReach, so an unreachable target (fled somewhere with no path) left them stuck chasing forever. Track ticks of no progress while still far from the target; if the familiar makes none for a short grace period it gives up and reverts to EmptyMode, which NPCTask turns back into following its owner. Moving, or being all but in range (just waiting on a free attack tile in a crowd), resets the grace period so genuine fights aren't abandoned. * Credit a familiar's kill to its owner When a familiar dealt the killing blow it was the npc's killer, but the death handler only drops loot (and grants slayer / logs the kill) when the killer is a Player, so familiar kills dropped nothing. Resolve a familiar killer to its owner so the loot drops for the player. * Fix crash resolving familiar owner on non-familiar npc kills A plain npc killer has owner_index -1, and Players.indexed(-1) throws ArrayIndexOutOfBoundsException - so every npc-on-npc kill crashed the tick. Only resolve an owner when owner_index is set. * Add familiar combat foundation Combat data and wiring the familiar combat behaviour builds on: - summoning.combat.toml / summoning_combat.anims.toml / .gfx.toml: per- familiar combat definitions (stats, attack styles, anims, projectiles). - summoning.npcs.toml: combat stats and combat_def links per familiar. - gameframe.ifaces.toml: summoning orb / follower-details "Attack" option. - Summoning.kt: apply the wilderness pvp combat form on summon and call. - NPCTask.kt: an idle familiar resumes following its owner instead of wandering. - Combat.kt: retaliation anchors its leash via leashAnchor() (owner tile for familiars). - Attack.kt: resolve an npc's swing definition through combat_def. * Familiar re-acquires the owner's current target when freed In multi-combat the familiar engaged the owner's target on combatStart and (correctly) never switched mid-fight, but once its own target died nothing re-engaged it if the owner had since moved to another enemy - it just stood idle. Assist on the owner's combatAttack too: each hit an idle familiar joins the owner's current (last-attacked) target, while the idle guard still leaves an actively fighting familiar alone. * Let familiars phase through players, not npcs With world.players.collision on, a player stamps BLOCK_PLAYERS on its tile and an npc's blockMove (BLOCK_PLAYERS|BLOCK_NPCS) is the canTravel extraFlag, so a familiar couldn't step onto any player tile - standing between it and its target blocked it. Drop BLOCK_PLAYERS from blockMove for owned followers (familiars/pets) so they phase through players including their owner, while keeping BLOCK_NPCS; solid npcs already stamp FLOOR so the pathfinder still routes the familiar around them. * Set standard familiar attack speed to 8 ticks Combat defs were left at the engine default of 4 (2.4s). Standard summoning familiar attack speed is 8 ticks (4.8s) - add attack_speed = 8 to every familiar combat def. * feat: add familiar attack projectiles and animations. * Drain summoning points over familiar lifetime Summoning was free: no summon cost was consumed, points never drained, and the lifetime timer read summoning_time_minutes which was set on no familiar, so the countdown never worked. Mirror the live game: - Gate summoning on having enough points and drain the pouch's summon_points on summon. - New per-tick SummoningDrain timer drains the player's summoning points evenly over the familiar's life (total = levelRequired - summonCost + 1), using the same integer-accumulator approach as PrayerDrain, and dismisses the familiar when points hit zero. - Populate per-familiar summoning_time_minutes (ported from 2009scape) so the duration timer counts down; convert the stray meerkats familiar_time. - Add a 0-60 special-move-points pool (varp 1177): initialised to 60 on summon and regenerated +15 every 30s by the familiar timer. Special moves now spend the pool via useFamiliarSpecial instead of draining summoning points; Unicorn Stallion's cure is the first to use it, driven by a new summoning_special_cost npc param. * feat: add familiar special moves with Howl projectile tuning Add the cast-button familiar special moves ported from 2009scape: combat, boost and utility specials plus their shared helpers, dispatcher, stubs and named anim/gfx data. Spirit Wolf Howl fires its tornado from the familiar with 2009scape projectile timing (delay 50, flight 46 + dist*10, curve 5) at ground height so it visibly travels instead of snapping from the owner. * adjust howl special move projectile for spirit wolf familiar. * fix: familiar special move casting, projectiles, hitsplats - Name familiar_details cast button component (79) so target specials actually dispatch instead of "Nothing interesting happens". - Tune Dreadfowl Strike projectile (delay/speed/curve) and remove the stray magic-bolt projectile from dreadfowl's normal melee attack. - Sync special-hit damage + impact gfx to projectile flight time so the hit lands with the projectile instead of on a fixed delay. - Attribute a familiar's hitsplats to its owner so the owner sees the blue 0/miss (block) splat, previously hidden from non-participants. - Stop target familiar specials firing twice: use approachRange(update=false) in the dispatch handlers and set the cooldown before the effect runs. Adds a re-entrancy regression test. * fix: wire albino rat as a forager with Cheese Feast - Configure albino rat as a beast-of-burden forager (capacity 30) with a forage_albino_rat table producing cheese, so its "Withdraw"/"Take" option opens the familiar inventory (was "Nothing"/"can't carry"). - Cheese Feast special now produces 4 cheese into the familiar's own inventory and plays the player summoning-cast animation (7660) with the cheese gfx (1384) on the rat, matching 2009scape. * Adjust player animation to correct animation for renewing summoning points at obelisk. * Adjust player animation to correct animation for renewing summoning points at obelisk. * Feat: add egg spawn special move for spirit spider familiar * fix: allow npcs to retaliate against player owned familiars. * fix formatting - ran spotless apply * bug: fix allows npcs to retaliate against familiars that use special move against them. * feat: add Burrow npc option to desert wyrm familiar The wyrm digs to the nearest ore rock within 7 tiles and drops it. Like the live game it only mines the tiers below silver (copper, tin, clay, blurite, iron), preferring the highest tier available; messages when no rocks are near. * feat: spirit scorpion venom shot fires a projectile at its owner The scorpion plays its wind-up graphic, then once it finishes shoots a slow venom bolt at the owner, with the charge graphic landing on the owner when the bolt arrives. Flight time and start/end heights are passed explicitly because the projectile shares cache graphic 1355 with the impact gfx and void keys graphic definitions by numeric id (the shared entry's height/flight fields would otherwise collide). * refactor: drop venom shot impact graphic, keep only the projectile Graphic 1355 has no owner-impact effect, so remove the venom_shot_owner gfx and the timed impact queue; Venom Shot now just plays the wind-up and fires the bolt. * Adjust poison damage from venom shot charge * feat: play cast animation, graphic and sound on every special move Move the owner's special-cast visuals (anim 7660 + gfx 1316) and sound (4161) into castFamiliarSpecial so they fire on every successful special, not just Cheese Feast. The albino rat now plays its own Cheese Feast anim + gfx on top. Also play the obelisk renew sound (4214) when renewing summoning points. * feat: add compost mound Generate Compost special move Cast on an empty compost bin to fill it with a full bin of ready compost, or supercompost on a 1-in-10 roll. The familiar animates and fires a projectile at the bin, filling it on impact. Removes the compost mound scenery stub. * feat: giant chinchompa Explode special move Detonates around the familiar hitting up to nine nearby foes (random 0-12) then dismisses it. Cast from the button, and auto-fires free on ~1/10 of the familiar's own attacks (no scroll/points spent). * feat: chinchompa auto-explode triggers when attacked, for free Move the ~1/10 auto-detonation from the familiar's own attacks to incoming hits (npcCombatDamage), and fire it directly instead of through the scroll/points gate so it costs no scroll, points, or owner scroll-throw flourish. A cooldown guard blocks re-entry. * fix: show owner the blue 0 splat when a familiar is hit The prior attribution only covered hits a familiar deals; a 0 hit landed on the familiar kept the attacker as the splat source, so the owner (neither victim nor source) never saw the blue block splat. Attribute splats on a familiar to its owner too. * fix: giant chinchompa attacks melee instead of ranged Its combat def used a range attack; switch to a melee crush attack at range 1 so it bites in melee like the live familiar. * feat: honey badger Insane Ferocity buff Wire the previously-inert special: enrage the owner, boosting Attack and Strength by 5 + 15% each at the cost of 10% Ranged, Magic and Defence, matching the scroll description. Boosts decay naturally and recasting refreshes them. Adds the owner enrage graphic. * feat: beaver Multichop, fletching and forage message Multichop: cast on a tree, the beaver walks over, chops (facing the tree, held in PauseMode so the idle-familiar watchdog doesn't drag it back), and stashes a log - possibly a lower tier than the tree - in its 30-slot pack. Interacting with it mid-chop is refused with a busy message, except using a log to fletch. Fletch: use a log on the beaver to fletch bows with no knife. Refactored the shared fletch flow to take a tool predicate; the beaver counts as a knife only while cutting, so a batch that outlasts the chop stops with "You need a knife to do that." Forage: foragers now message "Your familiar has produced an item." * feat: Void familiars' Call to Arms teleport special Ports the Call to Arms special (shared by the Void ravager, shifter, spinner and torcher familiars) that teleports the owner to the Void Knights' Outpost courtyard (2659, 2658, 0). The outpost island is on the map even though Pest Control itself isn't implemented, so the destination is safe. Registers the shared instant move in FamiliarUtilitySpecials with the 2009 teleport-out/teleport-in visuals, removes the four familiars from FamiliarSpecialStubs, and adds the named anim/gfx entries. Covered by FamiliarSpecialEffectTest. * feat: Void spinner heals its owner 100 life points every 15s Adds a passive heal-over-time for the Void spinner familiar via a shared `familiar_heal` timer, gated by the FAMILIAR_HEAL_LIFEPOINTS id map (same shape as FAMILIAR_BOOSTS). Started on summon and stopped on dismiss; restore no-ops at full life points so it only tops up after damage. * feat: Void shifter teleports owner to safety below 10% life points Hooks Constitution level changes on the shifter's owner: once their life points fall under 10% of maximum, it teleports them to the Void Knights' Outpost courtyard (the same tile Call to Arms uses). * test: cover Void shifter 10% threshold in life-point units Locks in that the shifter's below-10% trigger scales with life points (level 10 Constitution = 100 life points -> 10 life-point threshold), not the raw skill level. No behaviour change - getMax already returns life points. * feat: Bull Rush stuns its target Minotaur Bull Rush now stuns the target for 5 ticks on a real cast (via the shared stun primitive), on top of the tier-scaled ranged hit, so it can't act while the familiar charges in - completing the earlier TODO. * fix: stunned characters can't swing while charging in Stun set the target's delay/movement_delay/stunned clock, but the combat swing path (CombatMovement -> combatReached -> Combat.combat) only gated on action_delay. Movement was blocked, attacks weren't - so a minotaur's Bull Rush showed the stun graphic yet the target kept hitting. Bail out of combat() when the character is stunned; the stun then expires and the fight resumes on the next tick. Covers players too (e.g. a pickpocket stun landing mid-fight), not just Bull Rush. * feat: minotaur familiars swing their axe (gfx 1498) on normal attacks Every metal tier of minotaur familiar now plays the "minotaur axe swing" graphic on a normal melee attack, matching the live game. Adds a shared minotaur_axe_swing gfx def (id 1498) referenced by all six tiers. * fix: projectiles emit from the source edge facing the target * feat: Bull Rush stuns on impact, a third of the time, over a 2s charge The minotaur's Bull Rush projectile now flies for ~2s and its stun waits for the charge to land rather than firing the instant the special is cast, and only lands about a third of the time (1-in-3). familiarSpecialHit gains an onLand callback, queued to the hit's landing tick so on-impact effects run once the shot arrives. It runs a tick after the projectile flight - the same tick the damage lands, queued after the hit - so the stun (which sets the target's "delay" and would otherwise block the still -pending hit's queue) only fires once the damage has been applied. * feat: Macaw Herbcall searches out a grimy herb, on a 1-minute cooldown Reworks the Macaw's Herbcall special to follow 2009scape's MacawNPC: the familiar plays its search animation (anim 8013 / gfx 1321), then a few ticks later drops a random grimy herb at its feet. The herb is a uniform pick from all fifteen herb types (guam through torstol, low- and high-level alike), and the special has a one-minute cooldown of its own on top of the usual scroll and point cost. Previously it dropped a single grimy herb from a smaller pool at the player's feet instantly, with no animation or cooldown. * feat: Herbcall flies up, drops its herb mid-graphic, then recalls the macaw Sequences the Macaw's Herbcall around its animations: the macaw plays its fly-up/search animation on cast, its drop graphic bursts where it hovers about three seconds later, and the grimy herb lands a moment after that before the bird is called back down beside its owner. The queue delays account for two engine quirks - a queued action fires initialDelay + 1 ticks later, and a dropped floor item only becomes visible the following tick - so the herb settles roughly four seconds after the cast. * feat: cockatrice Drain option recasts Petrifying Gaze; Evil Flames plays its breath gfx and stops self-healing * fix: familiar specials deal life-point (x10) damage, matching normal attacks All special max hits and flat heals were written against 2009scape's 1x hitpoint scale, but familiar auto-attacks (and the rest of combat) use x10 life points - so every special hit for a tenth of its real value. Rescaled to the x10 reference values, and while in here: Arctic Blast now stuns small targets on impact 1 time in 5, and Crushing Claw drains 5% Defence, both as in the live game. * feat: nine familiar combat specials - Doomsphere, Spike Shot, Swamp Plague, Ebon Thunder, Mantis Strike, Deadly Claw, Acorn Missile, Famine and Inferno Karamthulhu's Doomsphere washes away 5% of the target's Magic; the swamp titan's plague poisons; the lava titan's bolt saps a player's special attack energy on impact; the praying mantis binds small targets in place (movement only - deliberately not the bull-rush stun); the talon beast rakes three times in a burst; the giant ent's acorns splash everything adjacent to the target; the ravenous locust turns a player's food rotten; and the forge regent burns another player's weapon and shield out of their hands. Values from darkan's x10 scale. * feat: Dust Cloud chokes up to seven nearby foes; Iron Within and Steel of Legends charge the titan's next swing into a flurry The smoke devil's Dust Cloud is a radius-1 burst hitting up to seven targets for up to 80 each. The iron and steel titans charge their next attack (melee-only for iron) to land 3 / 4 hits on their target - the charge is stored on the owner like the spirit scorpion's Venom Shot and spent by an npcCombatAttack handler, which clears the flag before the extra hits so re-fired events can't recurse. * feat: Regrowth revives a felled tree from its stump The hydra's object-target special fires the stump's pending regrow timer, bringing the tree back immediately; farming-patch trees are player-side variables, so only world stumps qualify. * feat: Swallow Whole, Ophidian Incubation and Immense Heat item-target specials The bunyip gulps a raw fish the owner could cook, healing the cooked value with no eat delay; the spirit cobra transmutes god-bird eggs into their cockatrice-family counterparts; and the pyrelord stands in for a furnace, opening the jewellery-crafting mould interface from a gold bar. With the last stubs implemented, FamiliarSpecialStubs is gone. * feat: familiar passives and item interactions - bunyip heal, forage xp, toad cannonball, ent transmute, regent firelighting, unicorn Cure fix The bunyip passively heals its owner 20 life points every 15s (with the water splash graphic); the granite lobster and ibis grant a tenth of the catch's Fishing xp on their foraged fish; Toad Bark now requires loading the barker toad with a cannonball and fires it for up to 300; the giant ent transmutes pure essence into earth/nature runes; the forge regent burns logs like the pyrelord (both now with the +10 xp helper bonus); and the unicorn stallion's Cure option actually cures - it checked a nonexistent 'poisoned' variable before - for 2 special-move points. * test: cover the combat, boost, item and passive familiar specials Two new suites invoke every newly-ported special and the rescaled ones: projectile hits, stat drains, stuns/binds (size-gated, so a 1x1 rat), the titan charge flurries, Dust Cloud, Toad Bark's cannonball cycle, the item-target specials through the real item-on-npc interaction, the Regrowth stump revival, and the passive bunyip heal, unicorn Cure, ent transmutes and forge-regent firelighting. Item-target specials moved from itemOnNPCApproach to itemOnNPCOperate: when the player is adjacent the interaction resolves the operate map, where the beast-of-burden store's *:familiar wildcard would swallow the click; operate's exact item:npc key wins instead. The titan flurry rolls its own flat max per extra hit (230 iron / 244 steel, from darkan) since NPC damage lives in combat defs, not npc-def max_hit params. * feat: cast button explains how to trigger item-target specials Immense Heat, Swallow Whole and Ophidian Incubation are cast by using the item on the familiar, so the familiar-tab/orb cast button had no registration and silently did nothing - it now messages the trigger (e.g. 'use a gold bar on the pyrelord') without spending anything. * feat: item-target specials cast from the button onto an inventory item A new FamiliarSpecialMoves.item map dispatches the familiar-tab/orb cast component used on an inventory item (spell-on-item style), so Immense Heat, Swallow Whole and Ophidian Incubation now work from the Cast option as well as by using the item on the familiar - both surfaces share one block per special. Winter Storage moves off item-on-npc entirely: the beast-of-burden store always wins that click when adjacent, so banking was unreachable. It now lives on Cast-on-item alone (refusing its own scroll), while using an item directly on the yak keeps storing it in the pack. * fix: pyrelord and forge regent breathe fire beneath themselves when burning logs The familiar now plays its fire-breathing animation (8085) and the fire and ashes land on the familiar's tile, instead of the player miming a tinderbox and lighting the fire underfoot. * feat: adjacent familiars face their idle owner; pyrelord steps west off its fire An npc following a target that is already within a tile now just faces them instead of shuffling onto the follow tile every step - it only moves again once the target walks out of reach. After lighting logs the pyrelord/forge regent steps one tile west when unblocked, like players do, then stands beside the fire facing its owner (resuming the follow immediately would walk it straight back onto the fire) until the owner moves off. * fix: familiar no longer tucks in behind an owner who steps up beside it The adjacency guard lived only in Follow.recalculate, but a freshly-restored Follow mode calculates a full path to the follow tile on its first tick (Movement.calculate), bypassing the guard - so after the pyrelord's step west, the owner walking up next to it sent it circling behind them. The guard now also runs in Follow.tick before any movement. * feat: logs land at the familiar's feet before the pyrelord breathes fire Using logs on the pyrelord/forge regent now drops them as a floor item on the familiar's tile (owner-visible, as a tinderbox drop would be), plays the fire-breath, and lights them two ticks later - aborting, fireless, if the logs are gone by then (e.g. picked back up). * feat: familiars step out from under a player standing on their tile Follow mode now mirrors combat movement's step-out: when the followed target stands on top of the npc (and it isn't allowed_under), it steps to a random free cardinal tile instead of idling underfoot. The adjacent-face guard exempts the overlap case so the step-out can run. * fix: Thieving Fingers actually boosts Thieving by 2 It was implemented as a visual-only flourish on the assumption the passive +3 covered it, but both references boost the stat on cast too - darkan raises Thieving by 2 with the owner sparkle (gfx 1300), on top of the magpie's passive invisible +3. * fix: a second same-tick graphic no longer overwrites the first Character.gfx checked the primary-graphic mask but flagged the secondary, so every graphic landed in the secondary slot and a second gfx() in the same tick clobbered the first - which is why Thieving Fingers' owner sparkle never showed: the cast gate's scroll flourish (1316) played right after it. Checking the secondary mask (the slot actually used) sends a simultaneous second graphic to the free primary slot, so both render. * fix: Blood Drain plays its visuals and restores stats by 2 + 20% The bloated leech's special ran with no visuals at all; per darkan it now plays the leech's draw animation (7715) and graphic (1419), sends the blood bolt (1417) to the owner with their burst graphic (1420), and the restore gains darkan's flat +2 on top of the 20% (adjustStat(2, 0.20)) - Levels.restore(skill, 2, 0.2) matches exactly. Cure, all-stat restore and the 25 life-point bite are unchanged. * fix: Tireless Run plays the right visuals on the right characters Per darkan: the 1521 graphic belongs on the terrorbird (with its 8229 flap animation, not 1009), the owner gets the 1300 sparkle - previously 1521 played on the player and the familiar showed nothing. The cast now also refuses (charging nothing) at full run energy, and the energy restored is half the boosted Agility level rather than the base. * fix: QA pass over every familiar special in the testing tracker - Abyssal Drain saps 10% Magic and 10% Prayer (was flat 1-3 Prayer) - Dissolve/Rending drain 10% of Attack/Strength (were flat 3/1), with their missing familiar and impact graphics (1361/1360, 1372) - Goad gores twice for up to 120 each (was engage-only); Ambush pounces the kyatt onto its target for up to 224 (was a recall-to-owner) - Dust Cloud is a combat special: the picked target plus up to six foes within seven tiles (was a radius-1 AoE around the familiar) - Famine no longer deals damage and rots a random (not first) food - Inferno gains its up-to-85 fiery bolt, castable on npcs too; the player-target disarm stays on top - Deadly Claw uses the talon beast's attack animation with tighter volley timing; Acorn Missile splash caps at nine and shows its projectile and impact per extra target - Iron Within / Steel of Legends are immediate volleys (3x230 melee / 3x220 magic, 4x244 melee/ranged by distance) with darkan's 7954/8190 animations - not charge-next-attack buffs - Boil picks melee up close, ranged or magic at even odds from afar, with the titan's own 1373 graphic; Ebon Thunder drains a tenth of remaining special energy (was flat 100) - Testudo boosts Defence by 8 (was 9); Volcanic Strength and Abyssal Stealth gained their missing animations/graphics; Magic Focus uses anim 8308; Titan's Constitution heals 10% of max (was flat 80) with per-titan colours and a full-health refusal; Healing Aura gets the same refusal and its owner sparkle (familiar keeps 1356) - Fish Rain drops one of shrimps/cod/bass/mackerel at the ibis' feet with its 8199/1337 visuals; Fruitfall shakes 0-6 fruit (papaya first, pineapple joins the pool); Essence Shipment also empties the titan's pack and plays 7698/1457; Winter Storage shows the yak's 1358 gfx * feat: familiar 'other abilities' from the testing tracker - Hunting cats teleport their owner home from the Interact option (graahk to the Karamja hunter area by the nature altar, kyatt to Piscatoris, larupia to Feldip Hills), refusing above level-20 wilderness; the lava titan's existing Lava Maze option now uses the same gated jewellery-style teleport instead of a bare tele - Using a raw fish on the bunyip transmutes it into 1 to heal/10 water runes (Swallow Whole moves to the Cast option alone) - The geyser titan boils a bowl into a bowl of hot water and recharges amulets of glory like the Fountain of Heroes - The ibis and granite lobster stand in for a harpoon when fishing - Ferocious: one in five of the spirit dagannoth's melee attacks strikes a second time (sync 7791/1427) - Passive boosts: the spirit graahk's missing Hunter +5, the stranger plant's Farming +1+4% and the geyser titan's Ranged +1+3% - the boost table now supports level-scaled formulas * feat: fishing foragers only produce while their owner fishes The granite lobster and ibis forage was already wired and rolling (a 1-in-12 chance per 30-second window, exactly darkan's 50-tick cadence) but rolled whether or not the owner was doing anything. Per darkan's canRollForager both now only roll while the owner is actively fishing, spearing swordfish/sharks (lobster) or tuna/swordfish (ibis) alongside them with a tenth of the catch xp. * feat: iron/steel titan and wolpertinger style-gated defence passives While summoned, the iron titan makes its owner 10% harder to hit with melee, the steel titan 15%, and the wolpertinger 5% harder to hit with magic - applied as a multiplier to the owner's defensive rating for the incoming attack style only (the tracker's 'boost only applies to melee/magic attacks'). Darkan only implements the steel titan's and unconditionally; the style gate follows the wiki. * feat: Drown and Flames order the familiar at its owner's target The karamthulhu overlord's Drown and smoke devil's Flames right-click options had no handler, so clicking them did nothing. Both now send the familiar at its current target (falling back to the owner's), firing its water/fire auto-attack - neither reference implements these (2009scape's Flames is a TODO stub), so they follow the wiki's 'hits opponent with a water/fire spell'. * fix: Dust Cloud fires as an AoE on a plain cast click Converting it to a target-then-cast combat special (darkan's shape) in the audit left a bare click on the cast button doing nothing until an npc was picked. A plain cast now chokes everything around the smoke devil itself (up to six foes, radius 1, as in 2009scape), while casting onto a specific foe keeps the darkan behaviour - that target plus up to six more around it. * fix: familiar AoE specials find npcs by their bodies, not anchor tiles Dust Cloud (and Fireball Assault, Sandstorm, Explode, the Acorn Missile splash) scanned spiral tiles against NPCs.at, which only matches an npc's south-west anchor tile - any monster larger than 1x1 standing right beside the familiar was invisible to the special and it reported nothing nearby to attack. The scan is now zone-based and measures the distance to the npc's bounds, nearest first. * fix: transformed npcs fall back to base-id interaction handlers Npc interactions resolve handlers by the transformed definition id, so a familiar in its wilderness combat form ('<id>_combat') stopped answering Interact, Store, Dismiss, Drown and item-on-familiar uses - every handler is keyed on the base id. Player-on-npc and item-on-npc interactions now fall back to the base id when the transform has no handler of its own, with transform-specific handlers keeping priority. * fix: stranger plant swings its own attack animation Its combat anims (8292/8294/8295) belonged to npcs 6829/6830 - an off-by-one-npc slip that darkan's stranger_plant.json also carries. 2009scape's npc config gives the stranger plant melee animation 8208 and death 8209, with no defence animation, so the defend entry is dropped rather than left pointing at the wrong model. * fix: Toad Bark's cannonball flies darkan's low rising arc The shot flew flat at the default height-40 line with no curve, which the client renders with the model oriented backwards. There is no projectile rotation in the protocol - the client orients the model along its trajectory - so the fix is darkan's launch profile: low out of the toad's mouth, rising slightly (10 -> 16, curve 16). * fix: familiar special projectiles fly at distance-scaled speed Special-move projectiles either flew near-instantly regardless of range (multiplier = 1, ~1 client tick per tile) or fell back to the fast 0 + 5/tile default. All are now delay = 30, time_offset = 10, multiplier = 10 - a flight of 1 game tick plus a third of a tick per tile, matching the live game's '1 + floor(distance / 3)' hit delay. Deliberate exceptions stay put: Bull Rush's constant 2s charge, the compost lob, and Venom Shot's explicit slow bolt. The standard is documented in the file header. * fix: Toad Bark fires the multicannon's round cannonball graphic The reference's 1402 projectile model faces backwards in this revision's cache, and the projectile protocol has no rotation - the client orients models along the trajectory alone - so no arc could right it. The dwarf multicannon's cannonball (gfx 53) is a plain sphere with no facing to get wrong; the low rising arc stays. * feat: cache graphic patching rotates the Toad Bark cannonball 180 degrees The projectile protocol carries no rotation (darkan's packet is field-identical - its 'angle' byte is our 'curve'), but the cache's graphic definitions do: opcode 6 is a model rotation in degrees (the revision's existing values are only ever 90/180/270, gfx 1402's was 0). Adds a GraphicEncoder mirroring the decoder, and a FixGraphics tool (fixGraphics gradle task, also wired into CacheBuilder) that patches gfx 1402 with a 180 degree rotation after round-trip-validating the re-encode - so Toad Bark keeps its smoke-trailed cannonball, flying the right way round. The local cache has been patched. * feat: Testudo plays its shell tint (gfx 1308) on the owner The war tortoise's cast showed only the familiar's own animation and graphic; the owner now gets the 1308 graphic alongside the scroll-cast flourish. The boost helper accepts an optional owner graphic for any future single-stat boosts that need one. * fix: the bunyip transmute projectile flies from the player to the bunyip The tossed fish flew out of the bunyip at its owner; it now travels the other way, from the player's throw to the bunyip's mouth. * fix: Fruitfall scatters fruit onto free tiles with landing splashes Per darkan, each shaken-loose fruit lands on its own free tile around the owner - deduped, splash graphic (1331) a tick before it appears - instead of the whole harvest piling up underfoot with no visuals. The first fruit is still always a papaya and an unlucky cast still drops nothing. * feat: the fruit bat flies up into the trees before dropping its fruit Fruitfall now plays 2009scape's two-stage sequence: the bat ascends (anim 8320 with the swirling-leaves graphic 1332), then ~4 ticks later swoops into its drop animation (8321) as the landing splashes flash and the fruit falls a tick after - the same staged-queue shape as the macaw's Herbcall. * fix: familiars follow the real PvP rules when sent at players familiarCanSpecial and commandFamiliarAttack required the in_pvp flag on both sides, but the wilderness only sets in_wilderness - so Famine, Ebon Thunder, Inferno and the attack command all refused with 'You can only attack players in a player-vs-player area' inside actual PvP. Player targets are now validated by Target.attackable with the owner as source - the same wilderness/PvP-area, combat-level-bracket and single-way rules as attacking directly. * fix: the arctic bear swings its own claws, not a spider's legs Its combat animations (5327/5328/5329) belong to the spider family - the spirit spider familiar two npc ids along uses the same set, and darkan's arctic_bear.json carries the identical off-by-one copy-paste. 2009scape's npc config gives the bear attack 4925, defend 4928 and death 4929, completing its own 4925-4929 cluster alongside the Arctic Blast special's already-correct 4926. * feat: Rise from the Ashes - the phoenix's special, built from the wiki Neither reference implements it: darkan's is a TODO stub ('this special is a pain in the ass to code') and 2009scape commented the scroll out, so the phoenix was deliberately left without a special until now. Per the wiki it has two modes: cast on a foe it blasts for up to the phoenix's 160 magic max and sears away half their Attack, Magic and Ranged, returned exactly ~6 seconds later (set back directly - restore caps at base level and would swallow it); cast on ashes the phoenix is reborn, the ashes burning away along with all its wounds. A plain cast click explains both triggers. * fix: Rise from the Ashes targets ashes on the ground, reborn-scaled The special is cast on a pile of ashes lying on the ground - not on a combat target or inventory ashes as first built. A new floor-item dispatch surface (FamiliarSpecialMoves.floorItem + cast-component onFloorItemApproach hooks) carries it: the phoenix drops in place and glows, then is reborn atop the ashes at full life points, scorching up to five foes beside them with magic hits scaling on the life points it was missing - a quarter of the deficit as the max hit, so an unhurt phoenix's rebirth is a pure heal. It stands over the spent ashes a few ticks (PauseMode) so the follow doesn't drag it back mid-flare. The live game's more-than-five glitch (rebirth but no attack) is not reproduced - the flare simply caps at five. * fix: the giant ent's transmuted runes stack inventory.replace swaps the essence for a rune in the same slot, so every transmute produced its own one-rune slot instead of joining the stack. Remove-then-add lets the rune merge with any existing stack. * fix: Titan's Constitution over-heals 80 life points past maximum The heal was a max-capped restore (darkan's 10%) with a full-health refusal; per the live game it boosts life points by a flat 80 that can carry the owner above their maximum, alongside the 12.5% Defence boost, shared by the fire, ice and moss titans. levels.boost on Constitution does exactly this, capping at max+260 like other constitution boosts. * fix: Spike Shot stuns its target on impact Darkan's version is damage-only, but per the wiki the spirit dagannoth's spike 'attacks the target, stunning them' - the stun now lands with the projectile (5 ticks, like Bull Rush) rather than the hit alone. * fix: combat specials fire at the familiar's foe on a plain cast click Iron Within (and every other npc-target special) only worked by clicking the button and then clicking a target - a plain click silently armed targeting mode, reading as broken. As in darkan and the live game, a plain click now auto-fires the special at the familiar's current combat target, falling back to the owner's, with a message when neither is fighting; clicking a target directly still works through the approach hooks. * fix: Steel of Legends' blade-storm graphic plays on the target alone The 1449 graphic showed on both the titan and its target; it belongs on the target tile only - the titan keeps just its attack animation. * fix: pack yak attack and defend animations The yak used darkan's 853/851 - another wrong-model set from its familiar combatdefs. 2009scape's npc config gives the pack yak's own cluster: attack 5782, defend 5783. * chore: strip narration comments from the special toml files, remove dead code The per-entry 'X's Y animation' comments in summoning_special.anims/gfx said nothing the entry names don't; only the load-bearing notes remain (suffix conventions, the projectile speed standard, Bull Rush's constant-flight exception, the Toad Bark arc/cache-rotation note, and Herbcall's tick maths). Dead code: dropForage (Fruitfall and Fish Rain place their drops per tile themselves now) and familiarBoostSkill (never called - the boost specials use their own helper), plus unused imports and commandFamiliarAttack's stale PvP-area-only doc. * test: wrap InterfaceApi.onItem calls in runTest - it is suspend on main * chore: adjust npc spawn for pikkupstix. * feat: Pikkupstix tutor dialogue and enchanted headgear scroll storage Pikkupstix gains his post-Wolf-Whistle Talk-to dialogue (2009scape transcript: the summoning overview, supplies shop, skillcape purchase) and an Enchant option. Enchanted headgear (from darkan's EnchantedHeadwear) stores combat scrolls: use combat scrolls on a hunter helm to charge it (antlers 40, lizard skull 65, feather headdress 150, one scroll type at a time), empty it back on a summoning obelisk, and a worn charged helm feeds a scroll to a special move when the pack has none. Only combat scrolls store (checked via the scroll -> pouch -> familiar -> npcTarget lookup); non-combat scrolls like Healing Aura are refused. For void's hunter helms the plain form is already the enchanted one (darkan's base==enchanted), so charging is the only visible state change. * feat: enchant metal/god helms for scroll storage; Commune and Uncharge options Extends enchanted headgear from the three hunter helms to darkan's full set - snakeskin bandana, archer/berserker/warrior/farseer/rune/splitbark helms, helm of Neitiznot, dragon med helm, lunar helm and armadyl helmet - each with its Summoning level and capacity. Unlike the hunter helms (whose plain form is already scroll-capable) these have the real three -state flow: Pikkupstix enchants the plain helm into its empty (e) form, scrolls charge it, and it disenchants back. The (e) middle-state items already exist in the cache; added their item defs. The charged helms already carry Commune and Uncharge in their cache option arrays but had no handlers, so the options did nothing. Commune now reports the stored scrolls and Uncharge empties them back to the pack (reverting to the enchanted form) - replacing the obelisk-empty stopgap. * feat: mirror stored scroll count onto the charged helm's item charge The count on the follower-details cast button (662,75) and the item is rendered client-side from the item's charge value, so the helm's stored scrolls have to live on the item, not just a server variable. The charged helms now carry a charges/charges_max cap, and the stored count is mirrored onto the worn or held helm's charge whenever it changes - so the number shows on the helm and feeds the cast-button count. Reverts reset the charge to 1 first so an emptied helm is a clean single item, not one carrying the old count. * feat: mirror stored scroll count onto the charged helm's item charge The count on the follower-details cast button (662,75) and the item is rendered client-side from the item's charge value, so the helm's stored scrolls have to live on the item, not just a server variable. The charged helms now carry a charges/charges_max cap, and the stored count is mirrored onto the worn or held helm's charge whenever it changes - so the number shows on the helm and feeds the cast-button count. Reverts reset the charge to 1 first so an emptied helm is a clean single item, not one carrying the old count. * fix: add the albino rat's missing summon animation The albino_rat_spawn entry was absent, so the familiar appeared with no animation on summon/call. 2009scape spawns familiars with a graphic and a player gesture rather than a per-npc animation, so there was no id to lift directly; identified 14859 from void's cache as the albino rat's summon animation - a 23-frame materialise matching the spirit wolf's summon (8298) profile exactly, from the rat's 14856-14863 cluster. * fix: remove albino rat's incorrect summon animation No valid rev-634 spawn animation exists for the albino rat: darkan's value (16080) is a post-634 re-animation, absent from this cache (max animation id 15260), and 2009scape plays no per-familiar spawn anim at all. Better to play nothing than a wrong animation. * feat: abyssal parasite turns Abyss prayer drain into gradual leak Entry no longer zeroes Prayer with a parasite summoned; instead 10 points leak every 8-16 ticks while inside. Losing the parasite in the Abyss drains the remainder, so dismissing it grants no free prayer. * feat: Drown instantly casts water blast for two summoning points Previously only re-targeted the overlord like a plain attack order. Now fires an immediate magic-based hit (max 76) with its attack visuals, charging two summoning points only when the cast succeeds. * feat: Flames instantly casts fire spell for two summoning points Previously only re-targeted the smoke devil like a plain attack order. Now fires an immediate magic-based hit (max 150) with its attack anim and a dedicated flame projectile (gfx 1473), charging two summoning points only when the cast succeeds. * fix: default familiar chatheads to small dialogue heads Familiars had large_head = true across the board, routing every dialogue through the large head component; the engine default of false matches the client. Also drops the per-familiar dialogue expression keys in favour of the cs2-driven chathead lookup. * feat: resolve familiar dialogue chatheads like cs2 script 751 Dialogue chatheads previously used the humanoid expression animations (or hand-maintained per-familiar toml sets, some wrong - the spirit wolf had the cat's). Familiars now resolve their animation at runtime exactly as interface 662's client script does: the follower_details_chathead_animation varbit value keys enum 1276, or enum 1275 with 50 subtracted when over 50. Also strips the _familiar suffix before setting the varbit so it transmits the mapped index instead of -1 (the map keys are base names), keeping the details interface in sync server-side. * fix: stop unmapped familiar chatheads playing the enums' default Familiars with chathead value 0 (arctic bear, albino rat, minotaurs) or an out-of-range second-bank key (unicorn stallion) have no entry in enums 1276/1275, so the lookup returned the enums' default - the cat's animation - and playing it on a mismatched head model crashed the client. Missing entries now fall back to the standard expression animations like any unmapped npc. * fix: replace typographic punctuation that crashes the client writeString sends each char as a single byte, so smart quotes (U+2018/9) and ellipses (U+2026) truncate to control bytes the client's font handling can't render - interacting with the arctic bear, talon beast or swamp titan crashed the game. Replace them with plain ASCII in all client-bound strings. * test: scan content sources for multi-byte client-bound characters writeString truncates characters over 0xFF to a control byte the client can't render, so smart quotes or ellipses in dialogue crash the game. Fails with file:line of any offending string, ignoring comments and interface option matchers. * fix: show static chatheads for familiars with unverified map values The varbit map's zeros are unverified gaps from the original capture, so familiars like the desert wyrm and arctic bear resolved no enum entry and bobbed with humanoid expression animations on non-human head models. Give them a static head (-1) instead, matching what the details interface shows, until their real values are identified. * fix: play the enums' default chathead animation like the client does The details interface animates value-0 familiars (desert wyrm, arctic bear) correctly because the client's datamap lookup returns the enum default - a generic head-bob - for missing keys. Match that instead of sending a static head; the earlier crash was the typographic punctuation, not this animation. * feat: resolve pet dialogue chatheads through the cs2 enum lookup talkToPet keyed the chathead enum by npc id - the wrong key space, the enums key on the varbit value - and passed the resulting animation name as an expression, which double-prefixed the lookup and rendered a static head. Mapped pets (sneakerpeeper) now resolve through familiarChatheadAnimation like familiars; unmapped pets fall back to the neutral expression. * fix: check the real level, not summoning points, in summoning gates Summoning's current level doubles as the points pool, so gates using Level.has - summoning a pouch familiar, dropping a pet, incubating an egg, enchanting headgear - wrongly refused players whose points had drained below the requirement. Switch them to hasMax, which reads the level derived from experience. Boostable-skill gates (bunyip's Cooking, pyrelord's Firemaking) are left on the current level. * Update game/src/main/kotlin/content/entity/death/NPCDeath.kt Much neater, good recommendation. Co-authored-by: Greg <GregHib@users.noreply.github.com> * Fix report abuse breaking the chat box (#1063) Opening report abuse in select-player mode (cs2 246) unhooks the chat input key listener (137:55) and sets report mode varc 11. The client's close script (cs2 675) never restores either, so chat stays dead until relog. The restore script (cs2 244) has no client-side callers - the server is expected to run it. Close the interface server-side on submit and run cs2 244 whenever report_abuse closes. Closes #1061 * Add pitfall traps (#1062) * Start on pitfall traps * Update pitfall messages * Fix bank withdraw full inventory message happening when all items were removed * Add pitfall level checks * Add pitfall collapse * Add pitfall logic and tests * Remove hunter class * Fix teasing, add tests for larupia and kyatts * Remove duplicate mazchna * Fix barbarian course advanced laps reward collection * Remove unused hunter data * A few account bug fixes (#1069) * Fix run orb status invalid on first login * Fix logging in requiring display name instead of account name * Fix being able to create an account using a display name that's already in use * Allow spaces in message request usernames fixes #1068 * Some script params * Fix account unit test * Fix account merge tests * Fix account definitions incorrectly using account name instead of display name as the key * Add random events (framework + events) (#1064) * Add random event system and maze event Implements the random event trigger, kidnap teleport and mysterious old man framework, plus the Maze random event where players navigate to a central shrine before time runs out. Tutorial Island is tagged no_random_events so events don't fire there. Ref GregHib/void-private#223 * Add in-place random event framework Extend the random event system beyond the kidnap-to-instance pattern so in-place nagging events (Certer, Sandwich Lady, Evil Twin) can be built: - RandomEvents.noteAndTeleport: ignore penalty that notes the player's inventory then exiles them, so an event can't be macroed through. - InPlaceRandomEvent + startInPlaceEvent/endInPlaceEvent: spawn an event NPC beside the player that follows and nags on a timer, applying the ignore penalty when its lifetime runs out. - Decouple entry from the old man: the trigger now calls RandomEvents.start directly and each launcher owns its intro. Kidnap events call the new mysteriousOldMan() helper; in-place events spawn their own NPC. Maze's launcher allocates its instance before the intro so a resumed timer doesn't fail the event mid-intro. - rewardCostumeOrCoins: shared collect-the-set reward (camo, lederhosen). Ref GregHib/void-private#223 * Add Freaky Forester random event The player is teleported to the forester's clearing and told to kill a pheasant with a specific number of tails. Only the assigned pheasant drops the correct raw pheasant; wrong ones drop an incorrect one that the forester rejects. Handing in the correct pheasant rewards the first missing lederhosen piece, or 500 coins if the set is complete. - Conditional pheasant drops keyed to the killer's assigned task - Blocks attacking further pheasants once one is killed - Static pheasant spawns in the clearing; forester dialogue drives the task - Removes the unconditional pheasant drop table (drops are now per-task) Ref GregHib/void-private#255 * Drop the raw pheasant to the floor instead of the inventory The assigned pheasant now drops the raw pheasant as a normal killer-owned floor drop (non-tradeable, private) for the player to pick up and hand to the forester, matching the live behaviour. * Add Sandwich Lady random event First in-place event on the framework: the Sandwich Lady appears beside the player pushing her tray and nags until spoken to. Talking opens her refreshment tray (interface 297); choosing the food she offered hands it over and ends the event in place, while any other choice knocks the player out via the note-and-teleport ignore penalty. Talking mid-combat hands over the correct food directly, and other players are turned away. Adds RandomEvents.completeInPlace for events that finish without a return teleport, and defines interface 297's seven food buttons (10-22). Ref GregHib/void-private#261 * Sandwich Lady reacts to the tray choice She now says "Hope that fills you up!" when handing over the correct food, and smacks the player for 3 before the knockout when they pick the wrong one, matching live behaviour. * Give the Sandwich Lady knockout its animation and dialogue - Show "Hope that fills you up!" as a chatbox line on a correct pick so the player actually sees it, instead of a fleeting overhead force-chat. - On a wrong pick she swings her baguette (animation), smacks the player, who plays the death animation and fades out before waking up elsewhere. * Add Drill Demon random event Sergeant Damien whisks the player to his exercise yard and barks an order. Each of the four mats shows an exercise on its sign (a varbit); the player uses the mat matching the order, performing the exercise animation. Four correct exercises earns a piece of the camouflage outfit (helmet/top/bottoms, first missing) or 500 coins if the set is complete. - Shuffles the four exercises across the mat signs each round - Exercise index 0 (jog) is read with a 0 default since the variable store clears vars set to their default value - Reuses the shared kidnap/complete and rewardCostumeOrCoins helpers Ref GregHib/void-private#254 * Fix Drill Demon signs and mat positioning - Encode the four exercises as sign varbit values 1-4 (0 = no sign). The variable store clears any var set to its default 0, so a sign set to 0 never reached the client - one mat showed no sign and the client's signs drifted out of sync with the server, making the correct mat read as wrong. - Walk the player onto the mat before performing the exercise. * Polish Drill Demon exercise and sign-change effects - On using a mat the player now faces the sergeant, plays the exercise sound and waits two ticks before performing the exercise animation. - When the signs reshuffle, a puff of smoke (gfx 2000) plays on each sign tile in turn from sign 1 to sign 4, a tick apart, as it changes. * Play the sign-change puff on the sign tile (mat y + 2) * Sweep the Drill Demon sign puffs half a tick apart (Option B) The four signs now all change on one tick and the smoke puffs sweep across them client-side, sign 1 to sign 4, half a tick (15 client ticks) apart, instead of one server tick between each change. * Use the large chathead for Sergeant Damien's dialogue * Show the sign item dialogue after Drill Demon's order After Sergeant Damien names the exercise, an item box shows the matching sign with "Go to this mat and <action>!" ("this mat" in red), matching 2009scape. Adds the missing push-up sign item (10946). Darkan doesn't implement Drill Demon. * Make Drill Demon wait before the first order On arrival the yard shows "Follow Sergeant Damien's orders!" and waits about five seconds before the sergeant barks the first exercise. Using a mat (or talking to him) before the order is given has him snap "I haven't given you the order yet, worm!" and earns no credit for that attempt. * Add Certer random event One of the certificate brothers appears beside the player, bows and nags. Talking opens the identify interface (184) showing an item and three descripti…
Summary
Fleshes out summoning familiars: talk dialogue, special moves for every familiar, foraging, skill boosts, passive abilities and overhead chat.
Talk dialogue & chatheads
<id>_combatform - transformed npcs fall back to their base id's interaction handlers.Special moves
Every familiar's cast-button special is implemented - combat hits, stat drains, stuns/binds, AoEs, boosts, heals, teleports, foraging drops and banking. Highlights:
Passive & other abilities
Player.familiarBoost(skill)now supports level-scaled formulas (stranger plant Farming +1+4%, geyser Ranged +1+3%) alongside flat boosts; the graahk's missing Hunter +5 restored.Target.attackable), not a Clan-Wars-only flag.Foraging
Engine fixes surfaced by this work
GraphicEncoder+FixGraphicstool, wired intoCacheBuilder) rotates the backwards-facing Toad Bark cannonball 180 degrees - the protocol itself has no projectile rotation.Overhead dialogue
Manual Testing
Tracked per-familiar in the QA sheet (special move, boosts, other abilities, in-game visuals):
https://docs.google.com/spreadsheets/d/14LjVE4VdZ8FEGrl42_MJjrY3iAfAV_6CJ1ZuV31r-eQ/edit?usp=sharing
In-game verification during development caught and fixed: silent cast buttons, backwards projectiles, wrong attack animations, PvP-area refusals, non-stacking runes, forager output, and follow/step-out movement quirks.
Unit Tests
game/src/test/kotlin/content/skill/summoning- all passing, including:FamiliarSpecialMoveTest- the scroll/points cast gate (spend-on-success, refusals)FamiliarSpecialEffectTest,FamiliarCombatSpecialEffectTest,FamiliarUtilitySpecialEffectTest- every special's effect through the registered blocks and, where the surface matters, the real dispatchers (orb cast clicks, item-on-npc, cast-on-item, dialogue options):engine:testand:game:testsuites greenReferences
Special Moves (Familiars lvl 1-40): https://www.youtube.com/watch?v=kduBCqBiLB0
Summoning Scrolls Knowledge Base Article: https://2011.rs/kb/summoning_scrolls
darkan (primary mechanics reference): local checkout,
Scroll.java/Pouch.java2009scape (animation/config reference): local checkout,
npc_configs.json