Convert and verify all melee/ranged/magic special attacks - #210
Open
jelqscape wants to merge 21 commits into
Open
Convert and verify all melee/ranged/magic special attacks#210jelqscape wants to merge 21 commits into
jelqscape wants to merge 21 commits into
Conversation
…ial attacks Rebuilt onto current main. This is the special-attacks-only subset of the special-attacks branch: the spawn menu (content/other/spawn, the admin spawn command), and the Lumbridge combat testing dummy have been excluded from this branch - they belong in separate PRs. 73 of 73 special attack weapons now compile, run, and (where they have real math to test) have unit test coverage. Highlights: - New shared subsystems: weapon poison (melee + ranged), Burn/venom/disease status effects, BindEffectService (shove-stun/bind), BlowpipeAmmo charge tracking, PvP area-attack manager, next-cycle ranged special timing, ProtectionPrayerLockout, BypassProtectionPrayerPlayerHitModifier, PowerOfDeathMeleeProtection, defenceMultiplier accuracy threading, PathingEntity.lastDamagingPlayerUuid (hooks into the existing shared recordDamage function - no new pipeline needed). - items.toml: real combat data (weaponCategory, attack animations, ammo categories) filled in for every weapon that had a working special attack but broken/missing normal-attack data - Bounty Hunter corrupted dragon weapons, Deadman mode variants, Trailblazer/Trailblazer Reloaded tools, Dinh's bulwark ornament, Emberlight, dual macuahuitl, Soulreaper axe (o), and more. - Two real engine bugs fixed at the source: ObjChargeManager using the wrong RSCMType for 3 of 4 functions, and this whole Trailblazer tool family's "Wield" sitting at cache op1 (this engine's default equip handling only fires on op2). - content/other/commands/AdminCommands.kt: two small testing commands (::me fills special energy, ::charge adds charges to a wielded weapon) used throughout this work, kept as real debug tooling. - content/other/consumables: a genuine, unrelated Dwarven rock cake fix (self-damage mechanic) and a PlayerInterfaceExtensions setColour bug fix (wrong RGB->5-bit conversion) picked up along the way. Full detail on every weapon and fix is in PROGRESS.md.
height = 96 was blind-copied from a Dragon claws template into most special attacks in this content pack, regardless of whether the effect actually belonged that high. Live-tested every affected weapon on special-attacks-pr: 21 read correctly at height = 0 (this commit), 15 were confirmed correct at the original 96 and left untouched (net-zero diff after round-tripping). Confirmed correct at height = 0: axe-boost group (StatBoostSpecialAttacks), Abyssal bludgeon, Ancient mace, Barrelchest anchor, Dragon claws, Dragon hasta, Dual macuahuitl, Rune claws, Statius warhammer, Vampyre flail, Vesta spear, Voidwaker, Armadyl crossbow, Ballista, Dragon crossbow, Eclipse atlatl, Morrigan's javelin, Webweaver bow, Zaryte crossbow. Confirmed correct at 96, untouched: Abyssal whip, Dragon dagger, Dragon longsword, Dragon mace, Dragon scimitar, Dragon sword, Magic bow, Magic shortbow, Dark bow, Seercull, Rune thrownaxe, Dragon thrownaxe, Saradomin sword, Saradomin blessed sword. Still unverified (left at 0 by default, not yet tested either way): Noxious halberd (no poison test yet), Brine sabre (needs underwater test), Dogsword (blocked by a crash before the animation even plays). Separate bugs found, not fixed here: Dogsword statHeal coerceIn crash; missing animations on Saradomin sword/blessed sword, Dragon crossbow, Magic bow/shortbow; wrong projectiles on Dark bow/Magic shortbow/ Seercull; Blowpipe normal-attack anim position; ranged weapons requiring melee distance to fire their special. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Belle's folly, cache-lock retry
- Dogsword ("Power of the Gods"): freeze effect was Player-only (`(target as? Player)?.let {...}`),
silently no-oping on NPCs. Wiki confirms Zamorak godsword's freeze is a real PvM tool (Muttadiles,
Barrows' Dharok), not player-only - wired through BindEffectService (Npc.movementLocked) to match
the real Zamorak godsword's own existing pattern in ImpactMeleeSpecialAttacks.kt. Both files also
now play spotanim.ice_barrage_impact on freeze ("Freezes opponent... with a similar animation to
Ice Barrage" per wiki), which neither had before.
- PlayerStatExtensions.statHeal: coerceIn(current, base) crashed with "Cannot coerce value to an
empty range" whenever current was already boosted above base (e.g. Dogsword healing a player who
was already overhealed) - coerceIn(current, maxOf(current, base)) instead, a shared fix covering
every heal-based special, not just Dogsword.
- obj.morrigans_javelin was missing param.attack_range entirely (fell back to melee-only range=1);
both cache variants (br_morrigans_javelin, morrigans_javelin_bh) already had it set to 5, the base
item just never got it.
- Added obj.belles_folly's weapon definition (weaponCategory + attack animations/sounds) - it had
none at all. Wiki confirms its attack animation is identical to the Ghrazi rapier, so this reuses
that entry's exact seqs/sounds/category.
- or-cache buildCache: CacheTool.initialize() (external dev.or2:tools dependency) intermittently
failed to overwrite SERVER/main_file_cache.idx255 while copying from LIVE
(FileAlreadyExistsException) - confirmed as an external OS-level lock (Windows Defender/Search
Indexer), not anything JVM-held, since it persisted with zero JVM processes running. Added a
retry-with-backoff around that call since we can't patch the external library directly.
VISUAL_QA.md added to track the broader live in-game verification pass this covers.
…che data Previous attempts at this file guessed at spotanim assets iteratively based on live-test feedback alone, without ever confirming against the underlying cache/animation data. Rewriting from the actual data instead: - seq.snapshot (1074) is confirmed as the real spec animation (matches AnimationID.SNAPSHOT / RANGED_MAGIC_SHORTBOW_SPEC referenced by several RuneLite plugins). Its frame data is two identical 27-client-cycle draw-and-release cycles back to back - the second arrow looses 27 client cycles after the first, not a full server tick apart. - spotanim.sp_attack_snapshot_spotanim (256) sits in the original RS2 spec-graphic id block (246-258) alongside sp_attack_puncture/cleave/shatter, all of which play on the attacker, and its own internal animation is a single draw-glow, not an impact effect. It belongs on the player once per shot, not on the target - putting it on the target (the previous version) was live-confirmed as the "weird effect on hit" bug. - spotanim.sp_attack_glow_arrow_travel (249) is what makes the arrow glow while actually in flight, confirmed live after an earlier version left the ammo's plain travel colour in place. - The second arrow's visual separation (both launch spotanim and ProjAnim) comes from a 27-client- cycle offset via ProjAnim.copy(), not a projanim swap (Dark bow's doublearrow_one/doublearrow_two pair has different angle/stepMultiplier in projectiles.toml - an intentional high/low-arc effect, confirmed wrong for this weapon) and not a delay() (whole-tick granularity, far too slow live). - Both hits register on the first arrow's tick (hitDelay/dropDelay both use firstProjectile's timing) - the second arrow's offset is visual only, not a real gameplay delay between the hits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… draw manager.spawnProjectile() dispatched the first arrow immediately using projanim.arrow's generic delay (41 client cycles), tuned for a normal single-draw bow shot. seq.snapshot's own draw-and-release cycle is only 27 cycles, so that default fired the arrow 14 cycles into the second draw instead of the first, and the second arrow (offset +27) landed after both draws had already finished. Build the ProjAnim by hand instead of going through spawnProjectile, so it can be dispatched at cycle 16 (where the held-draw frames end and the snap/release frames begin) rather than the wrong default. startTime and endTime are shifted by the same amount to preserve the real flight duration. Live-tested: arrows now fire alongside their own draw instead of both leaking out afterward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ot/Seercull glows Saradomin sword: replaced the target-only saradomin_lightning (76) with the real graphics from a reference implementation of this exact special (Zenyte-based Offline_Scape) - caster glow dh_sword_update_saradomin_ god_special_spotanim (1213, purpose-built, applied automatically by that engine's combat framework), target godwars_saradomin_magic_attack_ spotanim (1196, height 0, 30-cycle delay). Animation (1132) was already correct. Saradomin blessed sword: shares the base sword's caster/target graphics exactly, plus a third ground-location effect at the target's tile (godwars_saradomin_light_attk_spot, 1221) the base sword doesn't have - added WorldRepository/spotanimMap support to this file for it. Damage formula was already correct. Dark bow: was spawning two overlapping projectiles per arrow - the correct special one plus a second, separate one using the ammo's own plain colour, both on the same path at once. Dropped the redundant spawn (spotanim doesn't affect ProjAnim timing, only the projanim type does). Also fixed the two hits landing a tick apart at real range (doublearrow_one/two's differing stepMultiplier, meant only for visual arc, was leaking into damage timing) - both now resolve on the first arrow's tick. Magic bow (Powershot) and Seercull (Soulshot): both were using the ammo's plain launch/travel colour instead of a dedicated effect, same pattern as Magic shortbow's earlier fix. Powershot's glow (250) found by elimination in the project's own custom graphic block. Soulshot's graphics (472/473/474) found live via a temporary raw-numeric-id mode added to ::spot, since no external source had them - to be reverted before the PR (tracked in VISUAL_QA.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…hilate visuals Both confirmed against a reference implementation of these exact specials (Zenyte-based Offline_Scape / Near Reality, SpecialAttack.java). Dragon spear (and Zamorakian spear/hasta) Shove: caster glow height corrected to 96 - this session's earlier broad height=96->0 sweep had it at 0, live-tested as fine at the time, but the reference (Graphics(253, 0, 96)) and a live re-check both confirm 96 is right. Dragon crossbow Annihilate: was falling back to the weapon's plain normal-fire animation (playRangedWeaponFx) plus the ammo's own plain launch/travel colour - the actual cause of the animation reading as missing entirely. Real values: a dedicated fire-and-reload sequence (xbows_human_fire_and_reload), no caster-side spotanim at all (the reference passes a null graphic), a dedicated bolt-travel glow, and a real impact effect on target (firesurge_impact). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…agon hasta anim swap Sounds added to Cleave, Puncture, Sever, Shatter, and Dragon halberd's Sweep - all pure additions (nothing played before), confirmed against a reference implementation of these exact specials (Zenyte-based Offline_Scape/Near Reality). Their speculative height changes (96->100) were tried and then reverted back to 96 after live testing raised doubt about the whole batch - kept only what's independently solid: Puncture's hit-timing fix (NPC=1 tick, player=same tick), which the real wiki text confirms directly, not just this reference. Dragon halberd's Sweep had a real structural bug, not just a value mismatch: the direction graphic was attached to the caster's own entity instead of sent to a ground tile between caster and target, which is why it rendered at the player's feet facing them instead of sweeping toward the target. Fixed to compute the real target tile (or midpoint for larger targets) and dispatch a location-based graphic there via WorldRepository, at height 96 (previously unset, defaulting to 0) matching the reference. Dragon hasta's Unleash (brut_dragon_spear/Leagues variant) was using a generic placeholder animation that didn't sync correctly live; swapped to Sunspear's own thrust animation, which looks the same and is confirmed working. Magic shortbow's Snapshot: tried matching the reference's single-glow approach but live testing preferred the original per-draw version, so reverted back to it - kept only the newly-found sound. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lti-hit sound fix Added missing special-attack sounds (raw ids, unaliased in this cache's gamevals) to: Powershot, Chainhit, Soulshot, Saradomin's Lightning (x2), SWH Smash, Wild Stab, Spear Wall, Hamstring, Shield Bash, Impale, Eviscerate, Powerstab, Sanctuary/Lumber Up/Rock Knocker, and Voidwaker's Disrupt/Armadyl godsword's smash. Dragon claws' Slice and Dice previously played the wrong sound entirely (2537, actually Dragon dagger/Abyssal dagger's own Puncture sound, carried over by mistake from the reference port). Replaced with the real ids from the item's own wiki "Sound effects" table (4138/4140/4141, one per swing beat, third and fourth sharing a clip), and fixed a same-tick collision bug where firing all four soundSynth calls with no delay meant only the first one ever played client-side - staggered with delay = index * 20.
…ding bug + equip crash hardening Special attacks: added wiki-sourced sounds for Abyssal bludgeon (Penance), Barrelchest anchor (Sunder), Burning claws (Burning Barrage), Emberlight (Weaken), Tonalztics of ralos (Division, charged variant). Updated VISUAL_QA.md to reflect Dragon hasta's animation fix from earlier this session. Fixed "use scales on blowpipe/Webweaver bow" doing nothing: Zulrah's scales are also a Herblore ingredient (Extended antivenom+), and a generic "any herblore ingredient used on anything" catch-all was matching first whenever scales were the item clicked first, silently no-opping since blowpipe/bow aren't potion components - before dispatch ever got to try the reversed click order that would reach the real handler. Excluded scales from that catch-all in FinishedPotionsEvents, same pattern already used there for vial water/pestle/swamp tar. Hardened HeldEquipOp so an unexpected inventory transaction result fails the equip cleanly with a message instead of throwing an uncaught exception that disconnects the player. Added ::wornclear admin command (mirrors the existing ::invclear) to recover a worn slot that ends up in a bad state, since invclear only reaches the inventory bag.
…rge/uncharge edge case Craw's bow was entirely unimplemented (no normal attack, no charging) - added CrawsBowCharging (activate/top up/check/uncharge) and CrawsBowWeapons (normal ranged attack generating its own ammo, +50% accuracy/damage vs NPCs in the Wilderness while charged), mirroring the Webweaver bow it upgrades into - same activation cost (1000 ether), max stored ammo (16000), and Wilderness multiplier per the wiki. Also added the charged_variant/uncharged_variant params in items.toml that were missing from the cache entirely, required for ObjChargeManager to transform the item between its charged/uncharged forms. Fixed a real edge case in ObjChargeManager.addCharges: it used "0 stored charges" as a proxy for "this obj is the uncharged variant", but an obj can legitimately be the already-charged item with 0 stored charges (e.g. activated with exactly the minimum ether, none left over as ammo) - topping that up threw instead of adding to the same obj. Now checks the obj's own charged_variant param directly instead, matching the pattern already used by reduceWornCharges/removeAllCharges in the same file.
…g commands and progress docs Scoped this branch down to just special attacks/weapons and their directly associated changes, ahead of opening the PR: - Removed every admin command added on this branch that isn't in main (::me, ::charge, ::wornclear) and reverted ::spot/::item's cosmetic text changes to match main exactly - these were testing convenience tooling, not shipped content. - Deleted PROGRESS.md and VISUAL_QA.md - internal dev-tracking docs, not game content. - Trimmed AI-narrative-style comments across special-attacks, special-weapons, and the supporting herblore/objtx/equip files down to short factual notes - removed the "confirmed against a reference implementation", "this session", and "needs your eyes to confirm" style commentary that shouldn't ship in a PR. No behavioral changes - compiled and boot-tested clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dependency Removed every unit test file added on this branch (38 files across special-attacks, special-weapons, toxins, combat-commons, and consumables) - not shipping tests as part of this PR. Also removed the now-unused api.specials dependency from content/other/commands, left over from the already-removed ::me admin command. Compiles clean; remaining test suites for touched modules still pass.
Left over from the already-removed ::me admin command. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ghtmare spotanim heights - resolveCombatAttack classifies Nightmare staff's normal attack as melee (weaponCategory=Staff), so activateMeleeSpecial found the weapon's registered magic special and rejected it as the wrong type, silently clearing the special toggle and falling through to a normal hit. activateMeleeSpecial now redirects to activateMagicSpecial when the found special is SpecialAttack.Magic. - attackMagicSpell (the autocast/rune-cast path) never checked the special-attack toggle at all, so activating a special while autocasting a spell with the staff just cast the spell instead. Added the same toggle check/redirect used by the melee/ranged/staff paths, in both PvNCombat and PvPCombat. - Nightmare staff Immolate/Invocate: target hit spotanim height 96 -> 0. - Staff of the Dead family (incl. Toxic): Power of Death spotanim height 100 -> 300.
…not rendering The Weaken special's spotanim call omitted slot, defaulting to slot 0 - the same slot resetSpotanim() targets by default elsewhere in the engine, so the spin glow effect was liable to get clobbered before it rendered. Every other special attack in the codebase explicitly passes slot = constants.spotanim_slot_combat; this one didn't. Fixed to match.
…ight Crystal halberd's sweep reused Dragon halberd's red spotanim and the old player-attached spotanim() call, missing the ground-tile spotanimMap() (target's tile, or the midpoint for large NPCs, at height 96) that Dragon halberd was already upgraded to use. HalberdSpecialVisuals now takes a colour parameter (default red) instead of being hardcoded red-only; Crystal halberd passes the real white variant confirmed in the cache (dragon_halberd_special_*_white, ids 1232-1235).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Converts and verifies every melee/ranged/magic special attack (74 weapons) onto the current engine, plus every shared subsystem several of them need to function at all (weapon poison, status effects, charge-weapon tracking, stun/bind, PvP area attacks). A large number of these weapons also had no working normal attack at all - broken or entirely missing outside of their special - and that's fixed here too, listed separately below since it's a genuinely different body of work from the special attacks themselves.
Special attacks
All 74 melee/ranged/magic specials in
content/other/special-attacks, each verified against wiki-documented mechanics (damage formulas, accuracy multipliers, timing) rather than ported blind from any single source.Weapons that needed normal-attack work, not just their special
A special attack being correct doesn't mean the weapon could throw a regular punch - several of these had no normal-attack registration at all, or one that silently fell back to unarmed combat because the cache was missing the params
WeaponAttackManagerneeds to read:BlowpipeAmmo). Wiki-verified envenom/scale-consumption chances included.chargebowcategory refuses to fire without aWeaponMap, so it just silently did nothing. Added the normal attack, its own ammo-generation (revenant ether), and its Wilderness passive (+50% accuracy/damage vs NPCs).ObjChargeManagercharge primitive as the two bows above.weaponCategory/attack_anim_stance*/attack_sound_stance*cache params entirely, so the client's combat-style tab and the swing animation both fell back to generic unarmed punch/kick. Cache overrides added where the client needs them (weaponCategory), animations played directly where the engine's generic cache-param lookup had nothing to read. Thunder khopesh and Fang of the Hound also needed their real passives added (delayed lightning bolt; Flames of Cerberus proc).items.tomlgot real combat data (weaponCategory, attack animations, ammo categories) filled in for a broader set of weapons that had a working special attack but broken/missing normal-attack data: Bounty Hunter corrupted dragon weapons, Deadman mode variants, Dinh's bulwark ornament, Emberlight, and others.Shared subsystems (new, used by multiple weapons each)
WeaponPoisonEffect) - didn't exist at all before; (p)/(p+)/(p++) weapons were silently doing nothing.BurnEffectService,NpcPoisonEffectService, toxins package)BlowpipeAmmo- dart/scale charge tracking for the toxic blowpipe familyObjChargeManager- shared charge/uncharge primitive for ether/charge weapons (Webweaver bow, Craw's bow, Tumeken's shadow)BindEffectService- shove-stun/freeze-bind, shared by Dragon hasta's Shove and the godsword freeze specialsPvPAreaAttackManager,ProtectionPrayerLockout,BypassProtectionPrayerPlayerHitModifier,PowerOfDeathMeleeProtection- PvP-specific combat rules several specials needReal bugs fixed along the way
Not special-attack-specific, but found and fixed because a special attack or weapon exercised the broken path for the first time:
ObjChargeManagerwas using the wrong RSCM type for 3 of its 4 functions (crashed any real caller, including Tumeken's shadow) - fixed at the source, twice: once for the RSCM lookup, once for a "0 charges" edge case that broke topping up an already-activated Webweaver bow.PlayerStatExtensions.statHealcrashed on an already-overhealed target (coerceIngiven an empty range) - a shared fix covering every heal-based special, not just the one that surfaced it.resolveCombatAttackclassifies the staff's normal attack as melee (itsweaponCategoryis the plain meleeStaff, notPoweredStaff), so the special-activation dispatch always tried the melee path, found the weapon's registered magic special, rejected it as the wrong type, and silently cleared the special-attack toggle and threw a normal hit instead - with no error, so it looked "implemented" from reading the code alone. Same failure existed a second way: autocasting a spell with the staff resolves toCombatAttack.Spell, whose attack path never checked the special-attack toggle at all. Both paths now redirect into the magic special correctly. Only found via live testing - a pure damage-formula unit test can't catch a dispatch/classification bug like this, see "Testing" below.Testing
Most of this PR was verified live, in-game, weapon by weapon: correct animation/spotanim/sound, correct damage range against wiki-documented worked examples, correct timing (hit delays, projectile sync), and no crashes across repeated server restarts through every change. A small number of weapons got comparatively lighter live testing - see "Known limitations" below - and the Nightmare staff bug above is a concrete example of why live testing mattered here: it read as fully implemented and correctly registered from the code alone, and only broke visibly once actually swung in-game.
This branch also carried unit tests during development, covering the pure damage-formula math only (max hit scaling, multiplier stacking, etc.) checked against wiki worked examples independent of live dice rolls. They were useful for that narrow slice - catching formula regressions without needing a live server - but they exercise the damage functions directly and never touch special-attack registration, dispatch, or the actual combat-type classification a real attack goes through. The Nightmare staff bug above lived entirely in that untested gap: the damage formula was correct and would have passed its unit test every time, while the special never fired in-game at all. Deliberately not included in this PR's diff - the formulas themselves are already re-verified live, and the priority here is the game content, not test scaffolding - but worth naming explicitly so reviewers know what "tested" does and doesn't cover here.
Known limitations
🤖 Generated with Claude Code