Added first implemenation of Toreterror, shellstone, water cannon, toreterror arena, primordial armor
This commit is contained in:
1 parent
0e37e4069e
commit
70e4479f19
86 files changed
+1399
-115
No files matched your search
@@ -426,6 +426,7 @@ public final class AntarchySettings {
|
||||
private static double ductTapeRepairPercentPerUse = 0.25D;
|
||||
private static double ultimateArmorKnockbackResistance = 0.1D;
|
||||
private static double nightmareArmorKnockbackResistance = 0.1D;
|
||||
private static double primordialArmorKnockbackPerPiece = 0.5D;
|
||||
private static int fallenKingCrownArmorValue = 2;
|
||||
private static double fallenKingCrownArmorToughness = 0.0D;
|
||||
private static boolean elythiaFireflyParticlesEnabled = true;
|
||||
@@ -2275,6 +2276,9 @@ public final class AntarchySettings {
|
||||
public static double nightmareArmorKnockbackResistance() { return nightmareArmorKnockbackResistance; }
|
||||
public static void setNightmareArmorKnockbackResistance(double value) { nightmareArmorKnockbackResistance = value; }
|
||||
|
||||
public static double primordialArmorKnockbackPerPiece() { return primordialArmorKnockbackPerPiece; }
|
||||
public static void setPrimordialArmorKnockbackPerPiece(double value) { primordialArmorKnockbackPerPiece = value; }
|
||||
|
||||
public static int fallenKingCrownArmorValue() { return fallenKingCrownArmorValue; }
|
||||
public static void setFallenKingCrownArmorValue(int value) { fallenKingCrownArmorValue = value; }
|
||||
|
||||
|
||||
@@ -103,6 +103,12 @@ public final class AntarchyObjects {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> CRACKED_SHELLSTONE_BRICKS = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> MOSSY_SHELLSTONE_BRICK_STAIRS = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> MOSSY_SHELLSTONE_BRICK_SLAB = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> MOSSY_SHELLSTONE_BRICK_WALL = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> SHELLSTONE_STAIRS = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> SHELLSTONE_SLAB = (Supplier<Block>) UNBOUND;
|
||||
@@ -228,6 +234,9 @@ public final class AntarchyObjects {
|
||||
Supplier<Block> chiseledShellstone,
|
||||
Supplier<Block> mossyShellstoneBricks,
|
||||
Supplier<Block> crackedShellstoneBricks,
|
||||
Supplier<Block> mossyShellstoneBrickStairs,
|
||||
Supplier<Block> mossyShellstoneBrickSlab,
|
||||
Supplier<Block> mossyShellstoneBrickWall,
|
||||
Supplier<Block> shellstoneStairs,
|
||||
Supplier<Block> shellstoneSlab,
|
||||
Supplier<Block> shellstoneWall,
|
||||
@@ -305,6 +314,9 @@ public final class AntarchyObjects {
|
||||
CHISELED_SHELLSTONE = chiseledShellstone;
|
||||
MOSSY_SHELLSTONE_BRICKS = mossyShellstoneBricks;
|
||||
CRACKED_SHELLSTONE_BRICKS = crackedShellstoneBricks;
|
||||
MOSSY_SHELLSTONE_BRICK_STAIRS = mossyShellstoneBrickStairs;
|
||||
MOSSY_SHELLSTONE_BRICK_SLAB = mossyShellstoneBrickSlab;
|
||||
MOSSY_SHELLSTONE_BRICK_WALL = mossyShellstoneBrickWall;
|
||||
SHELLSTONE_STAIRS = shellstoneStairs;
|
||||
SHELLSTONE_SLAB = shellstoneSlab;
|
||||
SHELLSTONE_WALL = shellstoneWall;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ public class ToreterrorRenderer extends GeoEntityRenderer<ToreterrorEntity> {
|
||||
|
||||
public ToreterrorRenderer(EntityRendererProvider.Context context) {
|
||||
super(context, new ToreterrorModel());
|
||||
this.shadowRadius = 2.0F;
|
||||
this.shadowRadius = 1.6F;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@ public class WaterBombRenderer extends EntityRenderer<WaterBombEntity> {
|
||||
public void render(WaterBombEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
|
||||
poseStack.pushPose();
|
||||
poseStack.mulPose(this.entityRenderDispatcher.cameraOrientation());
|
||||
poseStack.scale(0.5F, 0.5F, 0.5F);
|
||||
float scale = entity.isHuge() ? 1.5F : 0.5F;
|
||||
poseStack.scale(scale, scale, scale);
|
||||
|
||||
VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityTranslucent(WATER_TEXTURE));
|
||||
PoseStack.Pose pose = poseStack.last();
|
||||
|
||||
+29
@@ -5,6 +5,7 @@ import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class AntarchyDamageSources {
|
||||
public static DamageSource krakenMauling(ServerLevel level, Entity attacker) {
|
||||
@@ -87,6 +88,34 @@ public final class AntarchyDamageSources {
|
||||
);
|
||||
}
|
||||
|
||||
public static DamageSource toreterrorSpin(ServerLevel level, Entity attacker) {
|
||||
return new DamageSource(
|
||||
level.registryAccess()
|
||||
.registryOrThrow(Registries.DAMAGE_TYPE)
|
||||
.getHolderOrThrow(AntarchyDamageTypes.TORETERROR_SPIN),
|
||||
attacker
|
||||
);
|
||||
}
|
||||
|
||||
public static DamageSource toreterrorJump(ServerLevel level, Entity attacker) {
|
||||
return new DamageSource(
|
||||
level.registryAccess()
|
||||
.registryOrThrow(Registries.DAMAGE_TYPE)
|
||||
.getHolderOrThrow(AntarchyDamageTypes.TORETERROR_JUMP),
|
||||
attacker
|
||||
);
|
||||
}
|
||||
|
||||
public static DamageSource waterSoaked(Level level, @Nullable Entity directEntity, @Nullable Entity causingEntity) {
|
||||
return new DamageSource(
|
||||
level.registryAccess()
|
||||
.registryOrThrow(Registries.DAMAGE_TYPE)
|
||||
.getHolderOrThrow(AntarchyDamageTypes.WATER_SOAKED),
|
||||
directEntity,
|
||||
causingEntity
|
||||
);
|
||||
}
|
||||
|
||||
private AntarchyDamageSources() {
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,24 @@ public final class AntarchyDamageTypes {
|
||||
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "cornea_stalk_prick")
|
||||
);
|
||||
|
||||
public static final ResourceKey<DamageType> TORETERROR_SPIN =
|
||||
ResourceKey.create(
|
||||
Registries.DAMAGE_TYPE,
|
||||
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "toreterror_spin")
|
||||
);
|
||||
|
||||
public static final ResourceKey<DamageType> TORETERROR_JUMP =
|
||||
ResourceKey.create(
|
||||
Registries.DAMAGE_TYPE,
|
||||
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "toreterror_jump")
|
||||
);
|
||||
|
||||
public static final ResourceKey<DamageType> WATER_SOAKED =
|
||||
ResourceKey.create(
|
||||
Registries.DAMAGE_TYPE,
|
||||
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "water_soaked")
|
||||
);
|
||||
|
||||
private AntarchyDamageTypes() {
|
||||
}
|
||||
}
|
||||
@@ -133,10 +133,19 @@ public class BomberEntity extends Monster implements GeoEntity {
|
||||
|
||||
@Override
|
||||
public boolean hurt(DamageSource source, float amount) {
|
||||
if (source.is(DamageTypeTags.IS_FIRE)) {
|
||||
if (source.is(DamageTypeTags.IS_FIRE) || source.is(DamageTypeTags.IS_EXPLOSION)) {
|
||||
this.primeFuse();
|
||||
return false;
|
||||
}
|
||||
if (this.detonating) {
|
||||
Entity attacker = source.getDirectEntity();
|
||||
if (attacker != null) {
|
||||
Vec3 knockDir = this.position().subtract(attacker.position()).normalize();
|
||||
this.push(knockDir.x * 0.5, 0.2, knockDir.z * 0.5);
|
||||
this.hurtMarked = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Entity directEntity = source.getDirectEntity();
|
||||
if (directEntity instanceof AbstractArrow arrow && arrow.isOnFire()) {
|
||||
|
||||
+150
-30
@@ -2,6 +2,7 @@ package com.craisinlord.antarchy.content.entity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.AntarchyObjects;
|
||||
import com.craisinlord.antarchy.content.damage.AntarchyDamageSources;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
@@ -25,6 +26,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minecraft.world.entity.ai.goal.MoveTowardsTargetGoal;
|
||||
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
||||
@@ -53,6 +55,8 @@ import java.util.UUID;
|
||||
public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
|
||||
private static final EntityDataAccessor<Integer> ANIMATION_STATE = SynchedEntityData.defineId(ToreterrorEntity.class, EntityDataSerializers.INT);
|
||||
private static final EntityDataAccessor<Boolean> PHASE_2 = SynchedEntityData.defineId(ToreterrorEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Boolean> JUMP_SHAKING = SynchedEntityData.defineId(ToreterrorEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
|
||||
private static final int ANIM_IDLE = 0;
|
||||
private static final int ANIM_WALK = 1;
|
||||
@@ -60,29 +64,37 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
private static final int ANIM_JUMP_ATTACK = 3;
|
||||
private static final int ANIM_SPIN = 4;
|
||||
private static final int ANIM_DEATH = 5;
|
||||
private static final int ANIM_RAPID_SHOOT = 6;
|
||||
|
||||
private static final int ATTACK_RAPID_FIRE = 0;
|
||||
private static final int ATTACK_HUGE_BOMB = 1;
|
||||
private static final int ATTACK_BOMBERS = 2;
|
||||
|
||||
private static final int DEATH_TICKS = 60;
|
||||
private static final int SHOOT_TOTAL_TICKS = 80;
|
||||
private static final int SHOOT_TOTAL_TICKS = 75;
|
||||
private static final int SHOOT_FIRE_START_TICK = 12;
|
||||
private static final int SHOOT_FIRE_END_TICK = 68;
|
||||
private static final int SHOOT_FIRE_INTERVAL = 8;
|
||||
private static final int RAPID_FIRE_INTERVAL = 4;
|
||||
private static final int RAPID_FIRE_END_TICK = 60;
|
||||
private static final int SHOOT_BOMBER_TICK = 12;
|
||||
private static final int SHOOT_BOMBER_SECOND_TICK = 32;
|
||||
private static final int JUMP_TOTAL_TICKS = 40;
|
||||
private static final int JUMP_LAUNCH_TICK = 10;
|
||||
private static final int JUMP_SHAKE_TICKS = 25;
|
||||
private static final int SPIN_TOTAL_TICKS = 40;
|
||||
private static final int SPIN_START_TICK = 3;
|
||||
private static final int SPIN_END_TICK = 22;
|
||||
private static final int SPIN_HIT_INTERVAL = 5;
|
||||
|
||||
private static final double RANGED_MIN_RANGE_SQR = 9.0D;
|
||||
private static final double CLOSE_RANGE_SQR = 64.0D;
|
||||
private static final double JUMP_RANGE_SQR = 400.0D;
|
||||
private static final double RANGED_MAX_RANGE_SQR = 625.0D;
|
||||
private static final double MELEE_RANGE_SQR = 9.0D;
|
||||
private static final double SPIN_RADIUS = 4.5D;
|
||||
private static final double JUMP_SHOCKWAVE_RADIUS = 6.0D;
|
||||
|
||||
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("Idle");
|
||||
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
|
||||
private static final RawAnimation SHOOT_ANIM = RawAnimation.begin().thenPlay("shoot").thenLoop("Idle");
|
||||
private static final RawAnimation RAPID_SHOOT_ANIM = RawAnimation.begin().thenLoop("shoot");
|
||||
private static final RawAnimation JUMP_ANIM = RawAnimation.begin().thenPlay("jump_attack").thenLoop("Idle");
|
||||
private static final RawAnimation SPIN_ANIM = RawAnimation.begin().thenPlay("spin_attack").thenLoop("Idle");
|
||||
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlayAndHold("death");
|
||||
@@ -98,9 +110,11 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
private int spinCooldown;
|
||||
private boolean jumpLaunched;
|
||||
private boolean shockwaveApplied;
|
||||
private boolean shootUseWaterBombs;
|
||||
private int jumpShakeTicks;
|
||||
private int shootAttackType;
|
||||
private boolean shootBomberFired;
|
||||
private int shootNextFireElapsed;
|
||||
private boolean shootBomberFiredSecond;
|
||||
private int rapidFireNextTick;
|
||||
private final Map<UUID, Integer> spinHitCooldowns = new HashMap<>();
|
||||
|
||||
public ToreterrorEntity(EntityType<? extends ToreterrorEntity> entityType, Level level) {
|
||||
@@ -112,7 +126,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
return Mob.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, AntarchySettings.toreterrorHealth())
|
||||
.add(Attributes.ATTACK_DAMAGE, 8.0D)
|
||||
.add(Attributes.MOVEMENT_SPEED, 0.25D)
|
||||
.add(Attributes.MOVEMENT_SPEED, 0.325D)
|
||||
.add(Attributes.FOLLOW_RANGE, 48.0D)
|
||||
.add(Attributes.KNOCKBACK_RESISTANCE, 0.85D)
|
||||
.add(Attributes.ARMOR, 10.0D);
|
||||
@@ -127,6 +141,8 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
super.defineSynchedData(builder);
|
||||
builder.define(ANIMATION_STATE, ANIM_IDLE);
|
||||
builder.define(PHASE_2, false);
|
||||
builder.define(JUMP_SHAKING, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,9 +150,10 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
this.goalSelector.addGoal(1, new ToreterrorRangedGoal());
|
||||
this.goalSelector.addGoal(2, new ToreterrorJumpGoal());
|
||||
this.goalSelector.addGoal(3, new ToreterrorSpinGoal());
|
||||
this.goalSelector.addGoal(4, new MoveTowardsTargetGoal(this, 1.0D, 64.0F));
|
||||
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 16.0F));
|
||||
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
|
||||
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.6D));
|
||||
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.5D));
|
||||
this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
|
||||
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::canTargetEntity));
|
||||
}
|
||||
@@ -151,6 +168,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
return switch (animState) {
|
||||
case ANIM_WALK -> state.setAndContinue(WALK_ANIM);
|
||||
case ANIM_SHOOT -> state.setAndContinue(SHOOT_ANIM);
|
||||
case ANIM_RAPID_SHOOT -> state.setAndContinue(RAPID_SHOOT_ANIM);
|
||||
case ANIM_JUMP_ATTACK -> state.setAndContinue(JUMP_ANIM);
|
||||
case ANIM_SPIN -> state.setAndContinue(SPIN_ANIM);
|
||||
case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM);
|
||||
@@ -171,6 +189,12 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
if (this.shootCooldown > 0) this.shootCooldown--;
|
||||
if (this.jumpCooldown > 0) this.jumpCooldown--;
|
||||
if (this.spinCooldown > 0) this.spinCooldown--;
|
||||
if (this.jumpShakeTicks > 0) {
|
||||
this.jumpShakeTicks--;
|
||||
if (this.jumpShakeTicks <= 0) {
|
||||
this.entityData.set(JUMP_SHAKING, false);
|
||||
}
|
||||
}
|
||||
|
||||
this.spinHitCooldowns.replaceAll((uuid, cd) -> cd - 1);
|
||||
this.spinHitCooldowns.entrySet().removeIf(e -> e.getValue() <= 0);
|
||||
@@ -193,6 +217,37 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hurt(DamageSource source, float amount) {
|
||||
if (!this.isPhase2()) {
|
||||
float halfHealth = this.getMaxHealth() * 0.5F;
|
||||
if (this.getHealth() - amount <= halfHealth) {
|
||||
float capDamage = this.getHealth() - halfHealth;
|
||||
if (capDamage <= 0.0F) {
|
||||
this.enterPhase2();
|
||||
return false;
|
||||
}
|
||||
boolean result = super.hurt(source, capDamage);
|
||||
if (result) this.enterPhase2();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return super.hurt(source, amount);
|
||||
}
|
||||
|
||||
private void enterPhase2() {
|
||||
this.entityData.set(PHASE_2, true);
|
||||
this.bossEvent.setColor(BossEvent.BossBarColor.RED);
|
||||
}
|
||||
|
||||
public boolean isPhase2() {
|
||||
return this.entityData.get(PHASE_2);
|
||||
}
|
||||
|
||||
public boolean isJumpShaking() {
|
||||
return this.entityData.get(JUMP_SHAKING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSeenByPlayer(ServerPlayer player) {
|
||||
super.startSeenByPlayer(player);
|
||||
@@ -283,17 +338,31 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
}
|
||||
|
||||
private void tickShootAnimation() {
|
||||
this.setAnimationState(ANIM_SHOOT);
|
||||
int elapsed = SHOOT_TOTAL_TICKS - this.shootAnimTicks;
|
||||
if (this.shootUseWaterBombs) {
|
||||
if (elapsed >= this.shootNextFireElapsed && this.shootNextFireElapsed <= SHOOT_FIRE_END_TICK) {
|
||||
this.shootNextFireElapsed += SHOOT_FIRE_INTERVAL;
|
||||
this.fireWaterBomb();
|
||||
switch (this.shootAttackType) {
|
||||
case ATTACK_RAPID_FIRE -> {
|
||||
this.setAnimationState(ANIM_RAPID_SHOOT);
|
||||
if (elapsed >= this.rapidFireNextTick && elapsed < RAPID_FIRE_END_TICK) {
|
||||
this.rapidFireNextTick += RAPID_FIRE_INTERVAL;
|
||||
this.fireRapidBomb();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.shootBomberFired && elapsed >= SHOOT_BOMBER_TICK) {
|
||||
this.shootBomberFired = true;
|
||||
this.fireBomberBurst();
|
||||
case ATTACK_HUGE_BOMB -> {
|
||||
this.setAnimationState(ANIM_SHOOT);
|
||||
if (elapsed == SHOOT_FIRE_START_TICK) {
|
||||
this.fireHugeBomb();
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
this.setAnimationState(ANIM_SHOOT);
|
||||
if (!this.shootBomberFired && elapsed >= SHOOT_BOMBER_TICK) {
|
||||
this.shootBomberFired = true;
|
||||
this.fireBomberBurst();
|
||||
}
|
||||
if (this.isPhase2() && !this.shootBomberFiredSecond && elapsed >= SHOOT_BOMBER_SECOND_TICK) {
|
||||
this.shootBomberFiredSecond = true;
|
||||
this.fireBomberBurst();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.getNavigation().stop();
|
||||
@@ -322,6 +391,8 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
if (this.jumpLaunched && this.onGround() && elapsed > JUMP_LAUNCH_TICK + 3 && !this.shockwaveApplied) {
|
||||
this.shockwaveApplied = true;
|
||||
this.applyJumpShockwave();
|
||||
this.jumpShakeTicks = JUMP_SHAKE_TICKS;
|
||||
this.entityData.set(JUMP_SHAKING, true);
|
||||
}
|
||||
this.jumpAnimTicks--;
|
||||
if (this.jumpAnimTicks <= 0) {
|
||||
@@ -344,16 +415,55 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
}
|
||||
}
|
||||
|
||||
private void fireWaterBomb() {
|
||||
private Vec3 calcLaunchVelocity(Vec3 from, Vec3 to, double minSpeed) {
|
||||
Vec3 diff = to.subtract(from);
|
||||
double dh = Math.sqrt(diff.x * diff.x + diff.z * diff.z);
|
||||
double dy = diff.y;
|
||||
double gravity = AntarchySettings.waterBombGravity();
|
||||
// Choose travel time based on horizontal distance at minSpeed, minimum 8 ticks
|
||||
double t = Math.max(8.0, dh / minSpeed);
|
||||
double vx = diff.x / t;
|
||||
double vz = diff.z / t;
|
||||
// Compensate for gravity accumulating over t ticks
|
||||
double vy = dy / t + 0.5 * gravity * t;
|
||||
return new Vec3(vx, vy, vz);
|
||||
}
|
||||
|
||||
private void fireRapidBomb() {
|
||||
if (!(this.level() instanceof ServerLevel serverLevel)) return;
|
||||
LivingEntity target = this.getTarget();
|
||||
if (target == null) return;
|
||||
WaterBombEntity bomb = new WaterBombEntity(this.level(), this);
|
||||
bomb.moveTo(this.getX(), this.getEyeY(), this.getZ(), this.getYRot(), 0);
|
||||
bomb.shootFromRotation(this, this.getXRot(), this.getYRot(), 0.0F, 1.4F, 1.5F);
|
||||
Vec3 from = new Vec3(this.getX(), this.getEyeY(), this.getZ());
|
||||
Vec3 to = new Vec3(target.getX(), target.getEyeY(), target.getZ());
|
||||
Vec3 baseVel = calcLaunchVelocity(from, to, 1.5);
|
||||
int count = this.isPhase2() ? 2 : 1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
double spread = count > 1 ? (i - 0.5) * 0.15 : 0.0;
|
||||
Vec3 vel = new Vec3(baseVel.x + spread, baseVel.y, baseVel.z - spread);
|
||||
WaterBombEntity bomb = new WaterBombEntity(this.level(), this);
|
||||
bomb.setPos(from.x, from.y, from.z);
|
||||
bomb.setDeltaMovement(vel);
|
||||
bomb.hasImpulse = true;
|
||||
serverLevel.addFreshEntity(bomb);
|
||||
}
|
||||
}
|
||||
|
||||
private void fireHugeBomb() {
|
||||
if (!(this.level() instanceof ServerLevel serverLevel)) return;
|
||||
LivingEntity target = this.getTarget();
|
||||
if (target == null) return;
|
||||
this.getLookControl().setLookAt(target, 30.0F, 30.0F);
|
||||
Vec3 from = new Vec3(this.getX(), this.getEyeY(), this.getZ());
|
||||
Vec3 to = new Vec3(target.getX(), target.getY(), target.getZ());
|
||||
Vec3 vel = calcLaunchVelocity(from, to, 0.9);
|
||||
WaterBombEntity bomb = new WaterBombEntity(this.level(), this, true);
|
||||
bomb.setPos(from.x, from.y, from.z);
|
||||
bomb.setDeltaMovement(vel);
|
||||
bomb.hasImpulse = true;
|
||||
serverLevel.addFreshEntity(bomb);
|
||||
}
|
||||
|
||||
|
||||
private void fireBomberBurst() {
|
||||
if (!(this.level() instanceof ServerLevel serverLevel)) return;
|
||||
LivingEntity target = this.getTarget();
|
||||
@@ -380,7 +490,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
List<LivingEntity> victims = this.level().getEntitiesOfClass(LivingEntity.class, shockArea, this::canTargetEntity);
|
||||
float damage = (float) AntarchySettings.toreterrorJumpAttackDamage();
|
||||
for (LivingEntity victim : victims) {
|
||||
victim.hurt(this.damageSources().mobAttack(this), damage);
|
||||
victim.hurt(AntarchyDamageSources.toreterrorJump(serverLevel, this), damage);
|
||||
Vec3 knockDir = victim.position().subtract(this.position()).normalize().add(0, 0.4D, 0);
|
||||
double knockback = AntarchySettings.toreterrorJumpAttackKnockback();
|
||||
victim.push(knockDir.x * knockback, 0.5D, knockDir.z * knockback);
|
||||
@@ -393,14 +503,14 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
|
||||
private void applySpinDamage() {
|
||||
if (!(this.level() instanceof ServerLevel)) return;
|
||||
AABB spinArea = this.getBoundingBox().inflate(SPIN_RADIUS, 2.0D, SPIN_RADIUS);
|
||||
AABB spinArea = this.getBoundingBox().inflate(SPIN_RADIUS, SPIN_RADIUS, SPIN_RADIUS);
|
||||
List<LivingEntity> victims = this.level().getEntitiesOfClass(LivingEntity.class, spinArea, this::canTargetEntity);
|
||||
float damage = (float) AntarchySettings.toreterrorSpinDamage();
|
||||
double knockback = AntarchySettings.toreterrorSpinKnockback();
|
||||
for (LivingEntity victim : victims) {
|
||||
UUID id = victim.getUUID();
|
||||
if (this.spinHitCooldowns.getOrDefault(id, 0) > 0) continue;
|
||||
if (victim.hurt(this.damageSources().mobAttack(this), damage)) {
|
||||
if (victim.hurt(AntarchyDamageSources.toreterrorSpin((ServerLevel) this.level(), this), damage)) {
|
||||
this.spinHitCooldowns.put(id, 20);
|
||||
Vec3 knockDir = victim.position().subtract(this.position()).normalize();
|
||||
victim.push(knockDir.x * knockback, 0.35D, knockDir.z * knockback);
|
||||
@@ -428,16 +538,26 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
if (target == null || ToreterrorEntity.this.isInAttackAnimation()) return false;
|
||||
if (ToreterrorEntity.this.shootCooldown > 0) return false;
|
||||
double distSqr = ToreterrorEntity.this.distanceToSqr(target);
|
||||
return distSqr >= RANGED_MIN_RANGE_SQR && distSqr <= RANGED_MAX_RANGE_SQR
|
||||
return distSqr > CLOSE_RANGE_SQR && distSqr <= RANGED_MAX_RANGE_SQR
|
||||
&& ToreterrorEntity.this.hasLineOfSight(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ToreterrorEntity.this.shootAnimTicks = SHOOT_TOTAL_TICKS;
|
||||
ToreterrorEntity.this.shootUseWaterBombs = ToreterrorEntity.this.random.nextFloat() < (float) AntarchySettings.toreterrorRangedWaterBombChance();
|
||||
ToreterrorEntity.this.shootNextFireElapsed = SHOOT_FIRE_START_TICK;
|
||||
ToreterrorEntity.this.shootBomberFired = false;
|
||||
ToreterrorEntity.this.shootBomberFiredSecond = false;
|
||||
ToreterrorEntity.this.rapidFireNextTick = 0;
|
||||
float roll = ToreterrorEntity.this.random.nextFloat();
|
||||
if (ToreterrorEntity.this.isPhase2()) {
|
||||
if (roll < 0.5F) ToreterrorEntity.this.shootAttackType = ATTACK_RAPID_FIRE;
|
||||
else if (roll < 0.65F) ToreterrorEntity.this.shootAttackType = ATTACK_HUGE_BOMB;
|
||||
else ToreterrorEntity.this.shootAttackType = ATTACK_BOMBERS;
|
||||
} else {
|
||||
if (roll < 0.5F) ToreterrorEntity.this.shootAttackType = ATTACK_RAPID_FIRE;
|
||||
else if (roll < 0.7F) ToreterrorEntity.this.shootAttackType = ATTACK_HUGE_BOMB;
|
||||
else ToreterrorEntity.this.shootAttackType = ATTACK_BOMBERS;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -462,7 +582,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
if (target == null || ToreterrorEntity.this.isInAttackAnimation()) return false;
|
||||
if (ToreterrorEntity.this.jumpCooldown > 0) return false;
|
||||
double distSqr = ToreterrorEntity.this.distanceToSqr(target);
|
||||
return distSqr <= RANGED_MAX_RANGE_SQR && ToreterrorEntity.this.onGround();
|
||||
return distSqr <= JUMP_RANGE_SQR && ToreterrorEntity.this.onGround();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -493,7 +613,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
LivingEntity target = ToreterrorEntity.this.getTarget();
|
||||
if (target == null || ToreterrorEntity.this.isInAttackAnimation()) return false;
|
||||
if (ToreterrorEntity.this.spinCooldown > 0) return false;
|
||||
return ToreterrorEntity.this.distanceToSqr(target) <= MELEE_RANGE_SQR && ToreterrorEntity.this.onGround();
|
||||
return ToreterrorEntity.this.distanceToSqr(target) <= CLOSE_RANGE_SQR && ToreterrorEntity.this.onGround();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.craisinlord.antarchy.content.entity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.AntarchyObjects;
|
||||
import com.craisinlord.antarchy.content.damage.AntarchyDamageSources;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
@@ -18,6 +19,8 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -26,6 +29,8 @@ import java.util.UUID;
|
||||
|
||||
public class WaterBombEntity extends ThrowableProjectile {
|
||||
|
||||
private static final EntityDataAccessor<Boolean> HUGE = SynchedEntityData.defineId(WaterBombEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
|
||||
private int lifetimeTicks;
|
||||
private final Map<UUID, Integer> hitCooldowns = new HashMap<>();
|
||||
|
||||
@@ -39,8 +44,18 @@ public class WaterBombEntity extends ThrowableProjectile {
|
||||
this.lifetimeTicks = AntarchySettings.waterBombLifetimeTicks();
|
||||
}
|
||||
|
||||
public WaterBombEntity(Level level, LivingEntity owner, boolean huge) {
|
||||
this(level, owner);
|
||||
this.entityData.set(HUGE, huge);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
builder.define(HUGE, false);
|
||||
}
|
||||
|
||||
public boolean isHuge() {
|
||||
return this.entityData.get(HUGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,8 +90,8 @@ public class WaterBombEntity extends ThrowableProjectile {
|
||||
UUID id = entity.getUUID();
|
||||
if (this.hitCooldowns.getOrDefault(id, 0) > 0) return;
|
||||
|
||||
float damage = (float) AntarchySettings.waterBombDamage();
|
||||
if (living.hurt(this.damageSources().thrown(this, this.getOwner()), damage)) {
|
||||
float damage = (float) AntarchySettings.waterBombDamage() * (this.isHuge() ? 3.0F : 1.0F);
|
||||
if (living.hurt(AntarchyDamageSources.waterSoaked(this.level(), this, this.getOwner()), damage)) {
|
||||
this.hitCooldowns.put(id, 15);
|
||||
Vec3 knockDir = living.position().subtract(this.position()).normalize();
|
||||
double knockback = AntarchySettings.waterBombKnockback();
|
||||
@@ -89,9 +104,18 @@ public class WaterBombEntity extends ThrowableProjectile {
|
||||
protected void onHitBlock(BlockHitResult result) {
|
||||
BlockPos hitPos = result.getBlockPos().relative(result.getDirection());
|
||||
Level level = this.level();
|
||||
if (level.isEmptyBlock(hitPos) && level.getBlockState(hitPos.below()).isSolid()) {
|
||||
level.setBlock(hitPos, Blocks.WATER.defaultBlockState().setValue(LiquidBlock.LEVEL, 2), 3);
|
||||
level.scheduleTick(hitPos, Fluids.WATER, 2);
|
||||
if (this.isHuge()) {
|
||||
for (BlockPos pos : BlockPos.betweenClosed(hitPos.offset(-1, 0, -1), hitPos.offset(1, 0, 1))) {
|
||||
if (level.isEmptyBlock(pos)) {
|
||||
level.setBlock(pos, Blocks.WATER.defaultBlockState(), 3);
|
||||
level.scheduleTick(pos, Fluids.WATER, 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (level.isEmptyBlock(hitPos) && level.getBlockState(hitPos.below()).isSolid()) {
|
||||
level.setBlock(hitPos, Blocks.WATER.defaultBlockState().setValue(LiquidBlock.LEVEL, 1), 3);
|
||||
level.scheduleTick(hitPos, Fluids.WATER, 2);
|
||||
}
|
||||
}
|
||||
this.discard();
|
||||
}
|
||||
@@ -100,11 +124,13 @@ public class WaterBombEntity extends ThrowableProjectile {
|
||||
public void addAdditionalSaveData(CompoundTag tag) {
|
||||
super.addAdditionalSaveData(tag);
|
||||
tag.putInt("LifetimeTicks", this.lifetimeTicks);
|
||||
tag.putBoolean("Huge", this.isHuge());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAdditionalSaveData(CompoundTag tag) {
|
||||
super.readAdditionalSaveData(tag);
|
||||
this.lifetimeTicks = tag.contains("LifetimeTicks") ? tag.getInt("LifetimeTicks") : AntarchySettings.waterBombLifetimeTicks();
|
||||
if (tag.contains("Huge")) this.entityData.set(HUGE, tag.getBoolean("Huge"));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,144 @@
|
||||
package com.craisinlord.antarchy.content.item;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.EquipmentSlotGroup;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.item.ArmorItem;
|
||||
import net.minecraft.world.item.ArmorMaterial;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.component.ItemAttributeModifiers;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public class PrimordialArmorItem extends ArmorItem {
|
||||
private final Type armorType;
|
||||
|
||||
public PrimordialArmorItem(Holder<ArmorMaterial> material, Type type, Properties properties) {
|
||||
super(material, type, properties);
|
||||
this.armorType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemAttributeModifiers getDefaultAttributeModifiers() {
|
||||
String prefix = "primordial_" + this.armorType.getName();
|
||||
ItemAttributeModifiers.Builder builder = ItemAttributeModifiers.builder()
|
||||
.add(
|
||||
Attributes.ARMOR,
|
||||
new AttributeModifier(
|
||||
ResourceLocation.withDefaultNamespace(prefix + "_armor"),
|
||||
getDefense(),
|
||||
AttributeModifier.Operation.ADD_VALUE
|
||||
),
|
||||
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
|
||||
)
|
||||
.add(
|
||||
Attributes.ARMOR_TOUGHNESS,
|
||||
new AttributeModifier(
|
||||
ResourceLocation.withDefaultNamespace(prefix + "_armor_toughness"),
|
||||
getToughness(),
|
||||
AttributeModifier.Operation.ADD_VALUE
|
||||
),
|
||||
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
|
||||
);
|
||||
|
||||
double knockbackResistance = getKnockbackResistance(this.armorType);
|
||||
if (knockbackResistance > 0.0D) {
|
||||
builder.add(
|
||||
Attributes.KNOCKBACK_RESISTANCE,
|
||||
new AttributeModifier(
|
||||
ResourceLocation.withDefaultNamespace(prefix + "_knockback_resistance"),
|
||||
knockbackResistance,
|
||||
AttributeModifier.Operation.ADD_VALUE
|
||||
),
|
||||
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
|
||||
);
|
||||
}
|
||||
|
||||
builder.add(
|
||||
Attributes.ATTACK_KNOCKBACK,
|
||||
new AttributeModifier(
|
||||
ResourceLocation.withDefaultNamespace(prefix + "_attack_knockback"),
|
||||
AntarchySettings.primordialArmorKnockbackPerPiece(),
|
||||
AttributeModifier.Operation.ADD_VALUE
|
||||
),
|
||||
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
|
||||
);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefense() {
|
||||
return super.getDefense();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getToughness() {
|
||||
return super.getToughness();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnchantable(ItemStack stack) {
|
||||
return stack.getCount() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean isSelected) {
|
||||
if (!level.isClientSide && this.armorType == Type.HELMET && entity instanceof LivingEntity living) {
|
||||
if (living.getItemBySlot(EquipmentSlot.HEAD) == stack) {
|
||||
living.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 40, 0, false, false, true));
|
||||
}
|
||||
}
|
||||
super.inventoryTick(stack, level, entity, slotId, isSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
|
||||
if (this.armorType == Type.HELMET) {
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.primordial_helmet_water_breathing").withStyle(ChatFormatting.AQUA));
|
||||
}
|
||||
String knockbackDealt = String.format(Locale.ROOT, "%.1f", AntarchySettings.primordialArmorKnockbackPerPiece());
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.primordial_armor_knockback_dealt", knockbackDealt).withStyle(ChatFormatting.YELLOW));
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.primordial_armor_set_bonus").withStyle(ChatFormatting.DARK_GREEN));
|
||||
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
|
||||
}
|
||||
|
||||
public static double getKnockbackDealtBonus(LivingEntity entity) {
|
||||
int pieces = 0;
|
||||
for (ItemStack armorStack : entity.getArmorSlots()) {
|
||||
if (armorStack.getItem() instanceof PrimordialArmorItem) {
|
||||
pieces++;
|
||||
}
|
||||
}
|
||||
return pieces * AntarchySettings.primordialArmorKnockbackPerPiece();
|
||||
}
|
||||
|
||||
public static boolean hasFullSet(LivingEntity entity) {
|
||||
return entity.getItemBySlot(EquipmentSlot.HEAD).getItem() instanceof PrimordialArmorItem
|
||||
&& entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof PrimordialArmorItem
|
||||
&& entity.getItemBySlot(EquipmentSlot.LEGS).getItem() instanceof PrimordialArmorItem
|
||||
&& entity.getItemBySlot(EquipmentSlot.FEET).getItem() instanceof PrimordialArmorItem;
|
||||
}
|
||||
|
||||
private static double getKnockbackResistance(Type armorType) {
|
||||
return switch (armorType) {
|
||||
case HELMET -> 0.20D;
|
||||
case CHESTPLATE, BODY -> 0.30D;
|
||||
case LEGGINGS -> 0.30D;
|
||||
case BOOTS -> 0.20D;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -293,4 +293,5 @@ public abstract class LivingEntityMixin {
|
||||
);
|
||||
original.call(level, particle, pos.x, pos.y, pos.z, dx, dy, dz);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,14 @@ package com.craisinlord.antarchy.mixins.gravity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.item.GravityGunItem;
|
||||
import com.craisinlord.antarchy.content.item.PrimordialArmorItem;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
@@ -27,4 +32,34 @@ public abstract class PlayerAttackMixin {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Primordial's full set bonus makes player melee knockback ignore the target's knockback resistance.
|
||||
*/
|
||||
@WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V"))
|
||||
private void antarchy$ignorePrimordialKnockbackResistance(LivingEntity target, double strength, double x, double z, Operation<Void> original) {
|
||||
Player player = (Player) (Object) this;
|
||||
if (!PrimordialArmorItem.hasFullSet(player)) {
|
||||
original.call(target, strength, x, z);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strength <= 0.0D) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.hasImpulse = true;
|
||||
while (x * x + z * z < 1.0E-5D) {
|
||||
x = (Math.random() - Math.random()) * 0.01D;
|
||||
z = (Math.random() - Math.random()) * 0.01D;
|
||||
}
|
||||
|
||||
Vec3 previousMovement = target.getDeltaMovement();
|
||||
Vec3 knockback = new Vec3(x, 0.0D, z).normalize().scale(strength);
|
||||
target.setDeltaMovement(
|
||||
previousMovement.x / 2.0D - knockback.x,
|
||||
target.onGround() ? Math.min(0.4D, previousMovement.y / 2.0D + strength) : previousMovement.y,
|
||||
previousMovement.z / 2.0D - knockback.z
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user