Skip to content

Summoning: complete familiar abilities (specials, passives, dialogue)#1052

Open
HarleyGilpin wants to merge 138 commits into
GregHib:mainfrom
HarleyGilpin:feat/summoning/interactions-and-dialogues
Open

Summoning: complete familiar abilities (specials, passives, dialogue)#1052
HarleyGilpin wants to merge 138 commits into
GregHib:mainfrom
HarleyGilpin:feat/summoning/interactions-and-dialogues

Conversation

@HarleyGilpin

@HarleyGilpin HarleyGilpin commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Fleshes out summoning familiars: talk dialogue, special moves for every familiar, foraging, skill boosts, passive abilities and overhead chat.

Talk dialogue & chatheads

  • Per-familiar Interact talk dialogue for ~60 familiars (random + context-aware lines).
  • Familiars use a large, static correct-model chathead.
  • Interact (and every other option) keeps working on the wilderness <id>_combat form - 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:

  • Cast surfaces: plain click (instant, or auto-fired at the familiar's current combat target as in the live game), click-a-target (npc/player/scenery), Cast-on-inventory-item (Swallow Whole, Immense Heat, Ophidian Incubation, Winter Storage) and Cast-on-ground-item (Rise from the Ashes). Item-target specials print a usage hint on a bare click instead of doing nothing.
  • Projectiles fly at the live game's distance-scaled speed (1 tick + 1/3 tick per tile).

Passive & other abilities

  • Hunting cat teleports (graahk/kyatt/larupia; lava titan's Lava Maze option now gated + animated), bunyip raw-fish -> water runes and 20 LP/15s passive heal, geyser titan bowl-boiling and glory recharging, giant ent essence -> earth/nature runes (stacking), pyrelord/forge regent log-burning beneath themselves (with the player-style step-west), spirit dagannoth Ferocious double-strike, ibis/granite lobster harpoon substitution and fishing-gated foraging with a cut of the xp, karamthulhu Drown / smoke devil Flames attack orders, cockatrice Drain recast, unicorn Cure (fixed - it checked a nonexistent variable).
  • Style-gated defence passives: iron/steel titan owners are 10%/15% harder to hit with melee, wolpertinger owners 5% harder with magic.
  • Data-driven 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.
  • Familiars follow the real PvP rules when sent at players (wilderness or PvP area + combat-level bracket via Target.attackable), not a Clan-Wars-only flag.

Foraging

  • Generic forager system: gathers from a forage_ drop table into the familiar inventory every ~30s, retrieved via Withdraw/Take (reuses the beast-of-burden UI). Covers 20 overworld foragers; the fishing foragers only produce while their owner fishes.

Engine fixes surfaced by this work

  • A second same-tick graphic no longer overwrites the first (owner sparkles now render alongside the scroll-cast flourish).
  • Familiar AoEs find npcs by their bodies, not just their anchor tile - large monsters beside the familiar count.
  • NPC followers face an adjacent idle owner instead of shuffling onto the follow tile, and step out to a free tile when stood on (as combat movement does).
  • Projectile model orientation: cache graphic patching (GraphicEncoder + FixGraphics tool, wired into CacheBuilder) rotates the backwards-facing Toad Bark cannonball 180 degrees - the protocol itself has no projectile rotation.

Overhead dialogue

  • Familiars shout a random overhead line ~every 30s; 83 familiars' lines sourced from the RS Wiki, stored as a data-driven familiar_overhead table.

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)
  • Beast-of-burden, forager, heal, and definition tests
  • Full :engine:test and :game:test suites green

References

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.java
2009scape (animation/config reference): local checkout, npc_configs.json

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 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.
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.
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.
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.
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.
…ead 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.
@HarleyGilpin HarleyGilpin changed the title Summoning: familiar combat and interactions Summoning: familiar special moves, passives and interactions Jul 6, 2026
@HarleyGilpin HarleyGilpin changed the title Summoning: familiar special moves, passives and interactions Summoning: complete familiar abilities (specials, passives, dialogue) Jul 6, 2026
@HarleyGilpin HarleyGilpin marked this pull request as ready for review July 6, 2026 01:05
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.
…e 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant