Movement¶
Dashes, teleports, stomps, and the ground check they often need.
MoveAbility¶
A held dash: the user is driven along their look direction for a few ticks and hurts what they pass through.
public class MyChargeAbility extends MoveAbility {
public MyChargeAbility(AbilityCore<MyChargeAbility> core) {
super(core);
}
@Override public float getDashCooldown() { return 160.0F; }
@Override public float getDamage() { return AkumaAbilityHelper.scaledDamage(6.0F); }
@Override public float getRange() { return 1.5F; }
@Override public double getSpeed() { return 1.8; }
@Override
public void onTargetHit(LivingEntity entity, LivingEntity target, float damage, DamageSource source) {
// knockback, an effect... only called when the damage landed
}
}
- Pressing starts the dash for
getHoldTime()ticks (default10); pressing again stops it. - Each tick, the user's velocity is set along the look direction at
getSpeed(). Gravity still applies unless you callsetNoGravity(). - Targets within
getRange()are hurt throughhurtBurst, at most once every 10 ticks each, andonTargetHitruns for each hit that landed. - The cooldown starts when the dash ends.
The damage is raw
Return AkumaAbilityHelper.scaledDamage(realHp) from getDamage().
Override getDamageSource(entity) for a custom source, and isParallel() if the dash must run alongside other continuous abilities.
DashAbility¶
The base mod's own DashAbility, with two hooks added: startContinuityEvent runs right after the native dash has moved the user, and endContinuityEvent right after the native stop and cooldown. Use them for animations and effects without reimplementing the dash. onTargetHit stays abstract.
InstantDashAbility¶
A charged teleport: the user charges briefly, then reappears further along their look direction, hitting everything on the path.
public class MyBlinkAbility extends InstantDashAbility {
public MyBlinkAbility(AbilityCore<MyBlinkAbility> core) {
super(core);
}
@Override public float GetDashDistance() { return 12.0F; }
@Override public float GetDamage() { return AkumaAbilityHelper.scaledDamage(5.0F); }
@Override public float GetChargeTime() { return 1.0F; }
@Override public float GetCooldown() { return 120.0F; }
}
- During the charge the user is rooted, and a player's camera cannot turn more than 45 degrees away from where the charge started.
- Pressing again during the charge cancels the dash: no teleport, and no cooldown.
- The teleport stops at the first block on the path, and short of the first projectile in the way. Every target along the path is hit once through
hurtBurst, over a width ofGetWidth()blocks (default2.5). Override it for a narrower or wider dash. - It plays the base mod's dash swoosh: override
hasSound()orPlaySound(entity)to change it, andhasParticles()/SpawnParticles(target)for hit particles. - The base mod's momentum check applies: a body that cannot jump cannot dash.
The damage is raw
Return AkumaAbilityHelper.scaledDamage(realHp) from GetDamage().
Animations started before the cooldown are cut off
The cooldown start event stops the ability's animationComponent, right after the teleport. An animation meant to be seen as the user arrives must be started after that, by overriding startCooldownEvent and calling super first:
StompAbility¶
A charged area stomp: the user is rooted while charging, then everything around them is hurt, and optionally the blocks around them are affected.
public class MyStompAbility extends StompAbility {
public MyStompAbility(AbilityCore<MyStompAbility> core) {
super(core);
}
@Override public int getRadius() { return 6; }
@Override public int getDamage() { return (int) AkumaAbilityHelper.scaledDamage(8.0F); }
@Override public double getCooldown() { return 240.0; }
@Override
protected void applyEffectToTarget(LivingEntity owner, LivingEntity target, IAbility ability) {
// only called when the damage landed
}
}
| Override | Default | Meaning |
|---|---|---|
isChargeable() |
true |
false stomps on the press |
getChargeTime() |
20 |
charge length |
isStuckWhileCharging() |
true |
root the user and play the aiming pose. Both are released too when the charge is stopped from outside |
getRadius() |
8 |
area radius |
getDamage() |
20 |
damage, raw |
getCooldown() |
200 |
cooldown |
hasEffectOnBlock() |
false |
whether applyEffectOnBlock runs on nearby blocks |
getBlockPredicate(entity), getBlockPos(entity) |
which blocks, around which centre | |
afterStomp(entity, ability) |
nothing | runs after the stomp |
Targets are hurt through hurtBurst, and applyEffectToTarget only runs when the damage landed. A blockPlacingHelper is provided and cleared at each use, to track blocks the stomp places.
getDamage() returns an int, and it is raw
Scale and round explicitly, as above. The default of 20 deals 8 real HP.
GroundAbility¶
A minimal base for techniques that must be used standing on the ground. It adds ensureOnGroundOrNotify(user), which returns false in mid-air and tells a player why.
private void onUseEvent(LivingEntity entity, IAbility ability) {
if (!this.ensureOnGroundOrNotify(entity)) {
return;
}
// ...
}
The same check is available anywhere as AkumaAbilityHelper.ensureOnGroundOrNotify(user).
TwoFormFlightAbility¶
Since 2.6.0. The flight of a Zoan with two flying forms, in the shape of the base mod's PhoenixFlightAbility: a PropelledFlightAbility (double jump to take off, a stamina gauge, velocity along the look vector) whose speed depends on the form held.
public class MyFlight extends TwoFormFlightAbility {
private static final Form FULL = new Form(MyFullPoint.INSTANCE, MyMorphs.FULL,
0.74F, 0.50F, 0.0052F, 0.0035F); // sprint and cruise speed, sprint and cruise acceleration
private static final Form HYBRID = new Form(MyHybridPoint.INSTANCE, MyMorphs.HYBRID,
0.60F, 0.41F, 0.0042F, 0.0028F);
public MyFlight(AbilityCore<MyFlight> core) {
super(core, 96, FULL, HYBRID); // the ceiling, in blocks above the ground
}
}
- The gate is the forms' point abilities being continuous, which is what the Phoenix checks, rather than the morph, which lags a tick behind on a switch.
- The speed is read off the morph that is active: what the body currently is decides how fast it flies.
speedMultiplier(entity)scales both the top speed and the acceleration, for an effect that makes the flier faster.getSpeedstays overridable for a technique that drives the flight (a swoop).
Switch the flight on and off with the forms, from each point's continuity events, with AkumaFlight:
this.continuousComponent
.addStartEvent((entity, ability) -> AkumaFlight.takeOff(entity, MyFlight.INSTANCE))
.addEndEvent((entity, ability) -> AkumaFlight.land(entity, MyFlight.INSTANCE));
The base mod's flight is not vanilla flight
PropelledFlightAbility never sets player.getAbilities().flying: it turns gravity off and drives the velocity itself. Read isNoGravity() if you need to know whether a player is flying, and never enable a flight through a pause (takeOff refuses to) - haki overuse, Seastone and protected areas pause a passive to take flight away.
NPCs that fly¶
Since 5.0.0. The base mod's flight works for players only: an NPC that draws the fruit takes the forms and stays on the ground. Give the flight passive a goal wrapper, FlightAbilityWrapperGoal, and the NPC flies from the server, with no input:
new AbilityCore.Builder<>(id, name, AbilityCategory.DEVIL_FRUITS, AbilityType.PASSIVE, MyFlight::new)
.addGoalWrapper((mob, core) -> new FlightAbilityWrapperGoal<>(mob, core).yieldTo(MyStoopAbility.INSTANCE))
- Any flight passive takes it: a
PropelledFlightAbilityflies at its own top speed, any other one (the base mod'sFlyAbility, players only too) atsetSpeedblocks a tick (0.5). - It takes off while the flight may be used (a flying form held: the same gate as a player's) and the target is seen, then circles the target 8 blocks above and 6 out (
setHeight,setRadius), and every 5 seconds makes a low pass for 1.5 seconds, a block and a half beside its head (setPassEvery(100, 30)). - After 20 seconds in the air it comes down and rests 10 seconds on the ground (
setFlyTicks(400),setRestTicks(200)): a player with no bow gets his chance. It also comes down when the target is gone, and falls when the form ends, as a player does. - While a dive (
DiveStrikeAbility) runs, or a technique named inyieldTo(a stoop that drives the body itself), the goal lets it move the body. It flies through every other one: a hold carried off, a haki or a buff that lasts the whole fight. - A technique used from right over the target (a stoop, a talon that seizes) goes on
AboveTargetAbilityWrapperGoal: it starts in the air withinsetReachblocks of the target side to side (3) andsetMinDropabove it (2), where the low passes take the NPC, and ends with its continuity. A dive from the circle (DiveStrikeAbility) goes on the base mod'sRepeater, which turns the NPC to its target first.
BlockPassageAbility¶
A technique during which its user passes through blocks: a dive under the ground, a walk through a wall. Nothing is dug: the blocks are passed through, as in the base mod's own Free Swimming.
public class MyDiveAbility extends BlockPassageAbility {
public MyDiveAbility(AbilityCore<MyDiveAbility> core) {
super(core);
this.addUseEvent(this::onUse);
}
private void onUse(LivingEntity entity, IAbility ability) {
if (this.isPassing()) {
this.endPassage(entity);
} else {
this.startPassage(entity, 160.0F);
}
}
@Override
protected Vec3 steer(LivingEntity entity) {
return walking(entity).scale(0.22D);
}
@Override
protected void onPassageEnd(LivingEntity entity) {
this.cooldownComponent.startCooldown(entity, 100.0F + this.passedTicks() / 2.0F);
}
}
steer(entity)gives the velocity of the tick, or null to leave the body its own.walking(entity)is the walking keys as a direction on the ground.onPassageTick(level, entity)runs on the server every tick: an effect to keep up, particles, a reason to end early.wayOut(level, entity)says where the body is put at the end. By default the first room straight above it, however far (AkumaRoom.firstRoomAbove).onPassageEnd(entity)runs once the body is out: the cooldown.- The base mod's restricted blocks (bedrock, portals) are never entered: a body found in one is set back where it last was and the passage ends. Do not steer into one:
isRestricted(level, pos)looks ahead.
The movement is driven where the body is simulated
A player's own client moves the player: a velocity set by the server alone is overwritten every tick. steer is asked on both sides and its answer used on the client for a player, on the server for anything else. Whatever it reads must be known on both sides: the keys, the look, the blocks around - not a field only the server sets.