Skip to content

Spiderman#53

Draft
duzos wants to merge 11 commits into
masterfrom
feat/spiderman
Draft

Spiderman#53
duzos wants to merge 11 commits into
masterfrom
feat/spiderman

Conversation

@duzos

@duzos duzos commented Jul 6, 2025

Copy link
Copy Markdown
Owner

Summary

Adds the Spider-Man kit: wall crawl (already shipped), web rope with three behaviour modes, scroll-wheel rappel, fall-damage immunity, spidey sense, and sfx. Now ships with three character variants (Miles, classic Spider-Man, Spider-Noir with a fedora).

Characters

  • Spider-Man (classic) - Steve skin, suit ID timeless:spiderman. Texture file spiderman.png. Same four-power kit as the others.
  • Miles Morales - Alex skin, suit ID timeless:spiderman_miles.
  • Spider-Noir - Alex skin, suit ID timeless:spiderman_spidernoir. Has a custom fedora: three head-parented cubes (crown 8x3x8, crown overlay 8x3x8, brim 11x1x11 tilted -2.5deg) on a dedicated SpiderNoirSuitModel extending AlexSuitModel. Texture is 64x128 - top half is the standard player skin, bottom half packs the fedora UVs (crown at U=0,V=64 / overlay at U=32,V=64 / brim at U=0,V=80).

All three share the same PowerList: web swing, wall climb, super strength, spidey sense.

Texture remap tooling

Two scripts under scripts/ translate bbmodel-authored skins onto Minecraft's standard player UV layout:

  • remap_spidernoir.py - reads SpiderNoir.bbmodel (128x128 source, custom UVs, separate fedora cubes) and writes a 64x128 destination using PlayerModelTrue.bbmodel for body/head/legs UVs, Alex 3-wide overrides for arms, and packed fedora UVs in the bottom half.
  • remap_spiderman.py - the simpler 64x64 -> 64x64 Steve case for the classic skin (source uses incorrect mirror_uv arm mappings; output uses the standard layout).

Both scripts match elements by name, copy each face's source UV rectangle into the destination UV rectangle, and apply per-axis flips when the source/dest UV signs differ. Re-runnable any time the source bbmodel or PNG changes.

Powers

Web rope (right-click, empty main hand)

A single WebRopeEntity projectile drives three modes, picked at fire time from the look-target:

  • Swing: looking at no entity, not sneaking. Anchors on block hit, applies a pendulum constraint pulling the player toward the anchor when distance > rope length. Length lerps smoothly toward targetLength. Now persists through ground contact - the on-ground disconnect was removed, so a rope anchored to a wall keeps pulling you across a rooftop or pulls you up from a standing start.
  • Zip: sneaking + looking at a block. Anchored rope ramps the player's velocity along the rope direction with a sin(t * pi/2) curve over 6 ticks, holds at peak speed, snaps to surface (small upward boost) on arrival. Auto-cancels on left-click or 50-tick timeout.
  • Bind: looking at a hostile within 32 blocks. Hits the entity, anchors to its body, applies high-level slowness/mining-fatigue/weakness for 8s, zeros their velocity each tick. A second right-click reels the bound entity toward the shooter at 0.55 m/tick for 30 ticks. Auto-releases on target damage >= 6 from another source, target death, or timeout.

Common: rope side (left/right arm) is server-decided based on body-yaw vs head-yaw delta with a small random roll. Anchors land on any block face including the floor (the projectile raycast has no face filter); combined with the persist-on-ground change, floor-mounted ropes are now usable.

Scroll wheel rappel

MouseMixin injects Mouse.onMouseScroll HEAD - while the local player has a swing-mode rope, the scroll event is consumed (no hotbar swap) and forwarded as WebRappelC2SPacket(direction). Server-side rappel(direction) adjusts targetLength clamped to [1.5, 48], plays a tripwire-click sfx with up/down pitch difference, and the rope length lerps toward target server-side at 0.18 per tick. Sneak no longer affects rope length (was unintuitive vs scroll input).

Swing release

Left-click during a live swing rope releases it with look * 0.45 + Y 0.35 boost.

Fall-damage immunity

SwingFallGrace is a UUID-keyed timer map. Every server tick of a swing/zip rope refreshes the grace; a swing release also grants it. SwingFallDamageMixin injects LivingEntity.computeFallDamage and handleFallDamage, returning 0/false during grace. Grace lasts 80 ticks past the last refresh.

Spidey sense (toggle, key 4)

Per-player NBT bool flipped by power 4. While enabled, SpideySenseTracker ticks each client tick:

  • Builds a threat list from hostile mobs and live projectiles within 12 blocks.
  • Per-threat intensity = proximity falloff + bonus from velocity-toward-player dot product (projectiles aimed at you spike hard).
  • EntityGlowMixin returns true from Entity.isGlowing for any threat with non-zero intensity, client-side only for the local sensing player. Other players see nothing.
  • SpideySenseHud overlays the vanilla vignette tinted red, alpha = max-intensity * 0.45 lerped, pulsing sin(age * 0.35) * 0.65-1.0.

Pose & rendering

  • Pose persistence (WebSwingState.postSwing): set when a rope is active and the player is airborne, cleared on ground contact. The fade-timer ramps up during either rope-live OR post-swing mode and only ramps down on landing. The grip arm pose only applies while a rope is present; the trail-arm/leg/body sway continues through the airborne tail of a swing.
  • Vertical velocity drives pose: trail-arm pitch, body pitch, and leg swing all read an absolute vertSpeed term so falls and climbs both extend the trail arm forward and bias the body forward (signed velocity made arms collapse inward on descent). Post-swing both arms rise to a fall-reach driven by vertSpeed plus the swing oscillator.
  • Body tilt (WebSwingRendererMixin on LivingEntityRenderer.setupTransforms): pitch driven by atan2(motion.y, horizSpeed), roll by lateral rope direction times motion magnitude (or by sideways body-local motion when the rope is gone). Targets clamped to +/-25 deg, smoothed via per-player tick-lerp at 0.18 to absorb constraint snaps.
  • Arm pose (WebSwingPower.applyPose): grip arm rotation derived from body-local rope direction; pitch = atan2(-lz, -ly), roll = -asin(lx). Trail arm + legs scale 0-45 deg with full 3D speed. Outward-roll sign: pivot at -X uses positive roll, pivot at +X uses negative roll.
  • Sneak suppression (BipedSwingPoseMixin): clears model.sneaking = false on biped setAngles HEAD when the entity is mid-swing, so the crouched pose doesn't show through. SuitModel.resetSneakPivots resets head/body/arm/leg pivots that get copied from the biped during swing.
  • Off-hand rendering (HeldItemRendererMixin + HeldItemRendererInvoker): when the rope is on the off-arm side, vanilla's renderArmHoldingItem is invoked manually for that arm so the suit's off-arm renders even though the slot is empty.
  • Hand position (WebRopeRenderer): during a swing the rope endpoint is shoulder + min(arm_length, rope_distance) * direction_to_rope, so the line attaches at the gripping fingertip rather than the rest-pose hand offset.
  • Existing arm-renderer fix: PlayerEntityRendererMixin.timeless$renderArm now reads arm.pivotX < 0 instead of player.getMainArm() == RIGHT, so the suit renders the correct arm bone for whichever side vanilla requested.

Controls

Input Effect
Right-click (empty main hand) Fire web (mode picked from look-target + sneak)
Right-click during bind Reel bound entity toward you
Left-click during swing Release with boost
Scroll wheel during swing Rappel up/down
Power key 1 Web swing (alternative trigger)
Power key 4 Toggle spidey sense

SFX

spiderman_shoot.ogg extracted from the archive branch, re-namespaced under timeless:. Plays on rope fire, reel, and spidey sense toggle (different pitches per cue).

Test plan

  • Right-click in air -> swing rope anchors on first block in look-line, pendulum applies
  • Sneak + right-click looking at a block -> zip pulls player to anchor, snap on arrival
  • Right-click looking at a hostile mob within 32 blocks -> mob frozen, slowness particles
  • Right-click again during bind -> mob reels toward player
  • Scroll up during swing -> rope shortens with click sfx; scroll past initial length extends to 48
  • Sneak during swing does NOT shorten rope or visibly crouch the model
  • Left-click during swing releases with forward+up boost
  • Falling 50+ blocks from a swing/zip releases without fall damage
  • Power key 4 toggles spidey sense; nearby hostile mobs glow with red vignette pulsing
  • In multiplayer, only the sensing player sees the glow/vignette - other players see no outline on the same entities
  • Fire web while standing on ground - rope anchors and pulls without instant disconnect
  • Web anchors to floor blocks; pendulum pulls the player horizontally
  • Swing pose (trail arm sway, leg swing) continues mid-air after rope releases, fades on landing
  • Falling fast extends the trail arm forward; rising fast does the same (no inward collapse)
  • Spider-Man (classic) and Spider-Noir suits equippable from creative; both apply the same kit as Miles
  • Spider-Noir's fedora follows the head with a slight forward brim tilt

@duzos duzos self-assigned this Jul 6, 2025
@github-actions github-actions Bot added S: Untriaged Status: Indicates an item has not been triaged and doesn't have appropriate labels. C: Textures Changes: Might require knowledge of spriting or visual design. labels Jul 6, 2025
duzos added 6 commits July 7, 2025 00:17
resolves three conflicts:
- power.java: trivial whitespace
- setregistry.java: kept both miles + moonknight registrations
- suititem.java: kept master's data attachment + iswearing gate, dropped branch's chest-only path
- milessuit.java: dropped a getanimationinfo override using a non-existent enum (AnimationInfo.RenderType.TORSO_HEAD); the default ClientSuit.getAnimationInfo handles per-slot visibility correctly
…cs, ceiling invert

replaces the placeholder wallclimbpower with the proper sneak-to-attach pattern:

- new util/wallcrawldata: serializable per-player state (side direction, attach timer, ceiling invert timer) stored on the suit-data attachment so it auto-syncs to clients via the existing s2c chain
- wallclimbpower now extends power directly (was togglepower<equipsoundsupplier> which didnt fit). server tick: detect attach via ground+sneak+raycast against 5 candidate sides (n/s/e/w/ceiling, range 1.5b); detect detach via jump (synced through the existing keybind packet) or landing on the floor. while attached, applies a small velocity bias toward the wall to keep the player stuck.
- new mixin/client/wallcrawlrenderermixin injects at tail of livingentityrenderer.setuptransforms. while inverttimer > 0 (player attached to a ceiling), interpolates a 180-deg rotation around z plus a height translation so the player flips upside-down smoothly. wall-side tilt (player rotated 90 to lie against vertical walls) is mechanical-only for now; visual tilt for vertical walls is a follow-up.
- mixin registered in timeless.mixins.json
v0.2 only treats http 200 as success. discord webhooks with file attachments return 204 (no content) which the action raised as `Failed to send webhook: 204`. half the recent runs flagged failed for this reason. v0.3 explicitly accepts 204 (one-line change in webhook.ts: `== 200` -> `== 200 || == 204`). pinned to its commit sha for the same supply-chain reason as the v0.2 pin.
…on climb

builds on the initial wall-crawl scaffolding with the missing pieces from the reference:

- mixin/WallCrawlTravelMixin: head-injects livingentity.travel, cancels vanilla when wall-crawl is enabled and the worn suit has wall_climb. ports the moveEntityWithHeading math from the reference - look pitch projects onto the wall plane (forward * sinPitch * 1.5 = vertical climb), strafe goes perpendicular along the wall surface using yaw. for ceilings, flat 2d motion in the ceiling plane. sticky bias (0.08 toward wall), away-component zero, 0.91 drag.

- wallclimbpower: tryAttach now uses look-direction raycast at range 3.0 (not cardinal-iteration at 1.5), stores side as the hit face, no longer requires airborne. tickShared sets nogravity while attached and runs three checks for detach: jump pressed, on ground past the 0.5-timer grace period, or the multi-point wall-probe raycast (eye + center + feet) misses entirely. that last one catches cresting the top of a wall - all three probes go horizontally past empty space, all miss, immediate detach.

- flightpower: gated hasflight/isflying on the worn suit having flight or boosted_flight. fixes a stale-flag bug where switching from iron man (which sets isflying=true on the suit-data attachment) to spider man (alex model) caused alexsuitmodel.rotateparts to apply the iron-man flight pose every frame on miles, overriding the walk cycle.

- stevesuitmodel + alexsuitmodel: explicit walk-cycle limb math (cos(limbAngle * 0.6662) * 1.4 * limbDistance for legs etc) in setAngles before super, so walk pose is guaranteed regardless of copyfrom propagation. animations layered on top.

- suitfeature: copyto(context) after setangles so post-animation pose pushes back to the player biped.

- timeless.mixins.json: registered wallcrawltravelmixin (common-side mixin)
…e, sfx

- web rope entity with swing (pendulum), zip (eased pull + ground snap), bind (immobilise + reel) modes
- right-click fires; mode picked from look-target (hostile -> bind, sneak+block -> zip, else swing)
- scroll wheel rappels rope length 1.5-48 with smooth lerp + click sfx
- left-click during swing releases with forward+up boost
- procedural arm pose: gripping arm tracks rope, trail arm + legs scale 0-45 deg with horizontal speed
- whole-body tilt via livingentityrenderer mixin, smoothed pitch/roll based on motion + rope direction
- biped sneak pose suppressed during swing; held-item renderer extended so off-hand renders when rope is on off-arm
- swing fall grace: 80-tick window cancels fall damage during and after rope use
- spidey sense toggle power: client-only outline on hostile/projectile threats, hud vignette pulses with proximity, threat tracker scores velocity-toward-player
- spiderman_shoot sfx pulled from archive
@github-actions github-actions Bot added A: Datagen Area: Datagen implementation & API. C: Audio Changes: Might require knowledge of audio. labels May 4, 2026
@duzos duzos added enhancement New feature or request and removed S: Untriaged Status: Indicates an item has not been triaged and doesn't have appropriate labels. labels May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A: Datagen Area: Datagen implementation & API. C: Audio Changes: Might require knowledge of audio. C: Textures Changes: Might require knowledge of spriting or visual design. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant