Skip to content

Lines: AkumaLines

A line between two anchors (a rope, a thread, a tether), drawn on the client as real geometry.

LineStyle style = LineStyle.builder()
        .color(0xFFD8CFB8)
        .width(0.06F)
        .ropeLength(12.0F)
        .striped(true)
        .build();

UUID line = AkumaLines.open(level, LineAnchor.hand(caster), LineAnchor.center(target), style);
// the target is reeled in; the line follows it every frame with no further packet
AkumaLines.close(level, line);

Why not particles

The usual way to fake a line is to step particles along a segment on the server. That costs one packet per step for every client in range, and the line stays frozen where it was spawned: it cannot follow a hand that moves or a body being hauled in. Up close it reads as dots, not as a rope.

A line drawn with AkumaLines is one packet. An end anchored to an entity is resolved on the client every frame, with the partial tick.

Anchors

Factory The end is
LineAnchor.point(vec) a fixed point
LineAnchor.entity(entity) / entity(entity, offset) the entity's feet, plus an offset on the world axes
LineAnchor.body(entity, fraction) / center(entity) a fraction of the way up the bounding box: 0 is the feet, 1 the top
LineAnchor.hand(living) / hand(living, hand) the entity's hand

Prefer body to entity for a living target. The fraction is read against the box the client sees at render time, so the end stays at the same place on a body that shrinks or changes morph.

hand copies FishingHookRenderer in first person, so the line leaves the hand on screen. In third person it is an offset from the body yaw, scaled with the body's height.

The third-person hand offset is a first guess

LineRenderer.HAND_SIDE, HAND_FORWARD and HAND_DROP still have to be tuned in game.

Style

Builder Default Meaning
color(argb) 0xFF7F6650 the colour of the whole line; alpha is honoured
endColor(argb) same as color the colour at the to end, for a gradient
width(blocks) 0.05 width in world units (the leash is 0.025), clamped to 0.005 to 2
taut() yes no gravity: a straight line
sag(blocks) the middle hangs this far below the straight line, at any distance
ropeLength(blocks) a rope of that length: slack while the ends are closer, straight once they are further apart
segments(n) 24 curve resolution, clamped to 1 to 64; a taut line needs only 1
fullBright(bool) false ignore world light so the line glows
striped(bool) false darken every other segment, the way the leash reads as a twisted rope
lifetime(ticks) 0 the client removes the line on its own; 0 keeps it until close
fadeOut(ticks) 0 fade the alpha out over the end of the lifetime

ropeLength is the setting that looks like a real line being hauled in. It uses the parabola approximation of a hanging rope, sqrt(3·d·(L − d) / 8). That formula is floored at (L − d) / 2, so a very slack rope still hangs when its ends meet.

Every value is clamped both when the style is built and when it is decoded.

Calls

Call Sent to
open(level, from, to, style) returns the id players of the level within the server view distance of either end
update(level, id, from, to, style) the same; it opens the line if the client does not have it. ⚠️ The lifetime restarts
close(level, id) the whole level, since a player who walked away still holds the line

All three do nothing when called on a client level, so an ability can call them without a side check.

A player who arrives later does not see the line

It was sent when the line opened. That is fine for a line lasting a second or two. Re-send a long-lived line with update from time to time.

Give a line with two point ends a lifetime

Apart from close, the client drops a line in only three cases. Its lifetime runs out. An entity end stays missing for 20 ticks. Or the player leaves the world or the dimension. A line with two point ends, no lifetime and a lost close stays on screen until the player disconnects. The client also keeps at most 256 lines, and drops the oldest beyond that.

Rendering

The line is drawn at RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS. It uses the leash shader, translucent, with no culling, as quads. The leash's triangle strip would join two lines written into the same batch with a stray triangle.

The ribbon turns to face the camera at every point, so it keeps its full width from any angle. The leash instead draws two fixed crossed ribbons.