diff --git a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BallistaArrowEntity.java b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BallistaArrowEntity.java index 4bd9571..ed06d70 100644 --- a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BallistaArrowEntity.java +++ b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BallistaArrowEntity.java @@ -1,33 +1,26 @@ package com.virus5600.defensive_measures.entity.projectiles; -import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.data.DataTracker.Builder; -import net.minecraft.util.hit.EntityHitResult; import net.minecraft.world.World; -import software.bernie.geckolib.animatable.GeoEntity; -import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; import software.bernie.geckolib.animation.AnimatableManager.ControllerRegistrar; import software.bernie.geckolib.animation.AnimationController; import software.bernie.geckolib.animation.AnimationState; import software.bernie.geckolib.animation.PlayState; import software.bernie.geckolib.animation.RawAnimation; -import software.bernie.geckolib.util.GeckoLibUtil; import com.virus5600.defensive_measures.entity.ModEntities; import java.util.Map; -public class BallistaArrowEntity extends TurretProjectileEntity implements GeoEntity { +public class BallistaArrowEntity extends KineticProjectileEntity { private static final Map ANIMATIONS; - private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); - - //////////////////// - /// CONSTRUCTORS /// - //////////////////// + // ////////////// // + // CONSTRUCTORS // + // ////////////// // public BallistaArrowEntity( EntityType entityType, World world @@ -35,11 +28,12 @@ public BallistaArrowEntity( super(entityType, world); this.setDamage(4); - this.setPierceLevel(this.getMaxPierceLevel()); } public BallistaArrowEntity(World world, LivingEntity owner) { this(ModEntities.BALLISTA_ARROW, world); + + this.setPierceLevel(this.getMaxPierceLevel()); this.setOwner(owner); } @@ -64,55 +58,6 @@ protected void initDataTracker(Builder builder) { } @Override - protected void onEntityHit(EntityHitResult entityHitResult) { - super.onEntityHit(entityHitResult); - - // Reduces the speed of the arrow when it hits an entity - Entity entity = entityHitResult.getEntity(); - // For reference, max armor points is 30 - int armor = entity instanceof LivingEntity livingEntity ? livingEntity.getArmor() : 0; - boolean hasHeavyArmor = armor > 15, - hasLightArmor = armor <= 15 && armor > 0; - - if (entity instanceof LivingEntity livingEntity) { - double targetH = livingEntity.getHeight(), - arrowH = this.getHeight(), - variance = arrowH * 0.125; // 12.5% of the arrow's height - - double arrowMin = arrowH - variance, - arrowMax = arrowH + variance, - reducedVelocity = 0.125; - - // Reduce velocity and pierce level based on the side of the entity hit - if (this.getPierceLevel() > 0) { - // If the arrow is smaller than the target or has heavy armor, reduce the pierce level by 2 and velocity by 50% - if (targetH > arrowMax || hasHeavyArmor) { - this.setPierceLevel((byte) (this.getPierceLevel() - 2)); - reducedVelocity = 0.5; - } - // If the arrow is almost the same size as the target or has a light armor, reduce the pierce level by 1 and velocity by 25% - else if ((targetH < arrowMax && arrowMin < targetH) || hasLightArmor) { - this.setPierceLevel((byte) (this.getPierceLevel() - 1)); - reducedVelocity = 0.25; - } - // Otherwise, just reduce the velocity by 12.5% without reducing the pierce level - } - - // Apply reduced velocity - this.addVelocity( - this.getVelocity() - .multiply(reducedVelocity) - .negate() - ); - } - - this.addVelocity( - this.getVelocity() - .negate() - .multiply(0.1) - ); - } - public byte getMaxPierceLevel() { return 5; } @@ -146,11 +91,6 @@ public void registerControllers(final ControllerRegistrar controllers) { ); } - @Override - public AnimatableInstanceCache getAnimatableInstanceCache() { - return this.geoCache; - } - /////////////////////// // STATIC INITIALIZE // /////////////////////// diff --git a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BulletEntity.java b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BulletEntity.java index 5fb4860..9fc3f10 100644 --- a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BulletEntity.java +++ b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/BulletEntity.java @@ -4,22 +4,16 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.data.DataTracker.Builder; -import net.minecraft.entity.data.TrackedData; -import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.item.ItemStack; import net.minecraft.world.World; -import software.bernie.geckolib.animatable.GeoEntity; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; import software.bernie.geckolib.animation.AnimatableManager; -import software.bernie.geckolib.animation.AnimationController; import software.bernie.geckolib.util.GeckoLibUtil; import com.virus5600.defensive_measures.entity.ModEntities; -public class BulletEntity extends TurretProjectileEntity implements GeoEntity { - protected static final TrackedData PIERCE_LEVEL = DataTracker.registerData(BulletEntity.class, TrackedDataHandlerRegistry.BYTE); - +public class BulletEntity extends TurretProjectileEntity { private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); //////////////////// diff --git a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/KineticProjectileEntity.java b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/KineticProjectileEntity.java new file mode 100644 index 0000000..dd0c393 --- /dev/null +++ b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/KineticProjectileEntity.java @@ -0,0 +1,145 @@ +package com.virus5600.defensive_measures.entity.projectiles; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.data.DataTracker.Builder; +import net.minecraft.entity.EntityType; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.world.World; + +public abstract class KineticProjectileEntity extends TurretProjectileEntity { + + // ///////////// // + // CONSTRUCTORS // + // ///////////// // + public KineticProjectileEntity(EntityType entityType, World world) { + super(entityType, world); + this.setDamage(4); + this.setPierceLevel(this.getMaxPierceLevel()); + this.setSpeedAffectsDamage(false); + } + + // /////////////// // + // PROCESS METHODS // + // /////////////// // + // PROTECTED + @Override + protected void initDataTracker(Builder builder) { + super.initDataTracker(builder); + } + + /** + * {@inheritDoc} + *
+ *

{@link KineticProjectileEntity}

+ *

+ * The method in {@code KineticProjectileEntity} reduces the speed of the arrow + * along with the pierce level based on the amount of armor points the entity has. + *

+ * In this implementation, the projectile will have the following behavior + * (assuming the projectile is affected by armor points): + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Armor PointsPierce Level ReductionVelocity ReductionBase Damage Reduction
Heavy Armor250%50%
Light Armor125%20%
No ArmorN/A12.5%5%
+ * + * @param entityHitResult {@inheritDoc EntityHitResult} + */ + @Override + protected void onEntityHit(EntityHitResult entityHitResult) { + super.onEntityHit(entityHitResult); + + if (this.armorAffectsPiercing()) { + // Reduces the speed of the arrow when it hits an entity + Entity entity = entityHitResult.getEntity(); + // For reference, max armor points is 30 + int armor = entity instanceof LivingEntity livingEntity ? livingEntity.getArmor() : 0; + double dmgReduction = 0.05; + boolean hasHeavyArmor = armor > 15, + hasLightArmor = armor <= 15 && armor > 0; + + if (entity instanceof LivingEntity livingEntity) { + double targetH = livingEntity.getHeight(), + arrowH = this.getHeight(), + variance = arrowH * 0.125; // 12.5% of the arrow's height + + double arrowMin = arrowH - variance, + arrowMax = arrowH + variance, + reducedVelocity = 0.125; + + // Reduce velocity and pierce level based on the side of the entity hit + if (this.getPierceLevel() > 0) { + // If the arrow is smaller than the target or has heavy armor, reduce the pierce level by 2, velocity by 50%, and base damage by 50% + if (targetH > arrowMax || hasHeavyArmor) { + this.setPierceLevel((byte) (this.getPierceLevel() - 2)); + reducedVelocity = 0.5; + dmgReduction = 0.5; + } + // If the arrow is almost the same size as the target or has a light armor, reduce the pierce level by 1, velocity by 25%, and base damage by 20% + else if ((targetH < arrowMax && arrowMin < targetH) || hasLightArmor) { + this.setPierceLevel((byte) (this.getPierceLevel() - 1)); + reducedVelocity = 0.25; + dmgReduction = 0.2; + } + // Otherwise, just reduce the velocity by 12.5% and base damage by 5% without reducing the pierce level + } + + // Apply reduced velocity + this.addVelocity( + this.getVelocity() + .multiply(reducedVelocity) + .negate() + ); + + // Apply reduced damage + this.setDamage(this.getDamage() * (1 - dmgReduction)); + } + + this.addVelocity( + this.getVelocity() + .negate() + .multiply(0.1) + ); + } + } + + // /////////////////////////////// // + // GETTERS, SETTERS, AND QUESTIONS // + // /////////////////////////////// // + + // PUBLIC + /** + * An overridable method that determines whether an armor point affects the + * piercing of the projectile. + *

+ * By default, this method returns {@code true}. + * + * @return {@code true} if the armor point affects the piercing of the projectile, + * {@code false} otherwise. + */ + public boolean armorAffectsPiercing() { + return true; + } +} diff --git a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/TurretProjectileEntity.java b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/TurretProjectileEntity.java index 06193dd..a4e8581 100644 --- a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/TurretProjectileEntity.java +++ b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/TurretProjectileEntity.java @@ -1,5 +1,6 @@ package com.virus5600.defensive_measures.entity.projectiles; +import com.virus5600.defensive_measures.entity.turrets.TurretEntity; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.component.DataComponentTypes; @@ -46,11 +47,11 @@ import org.jetbrains.annotations.Nullable; -import java.util.List; import java.util.Objects; -import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; +import software.bernie.geckolib.util.GeckoLibUtil; /** * {@code TurretProjectile} is an abstract class that acts nearly akin to {@link PersistentProjectileEntity} @@ -67,15 +68,13 @@ public abstract class TurretProjectileEntity extends ProjectileEntity implements protected static final int CRITICAL_FLAG = 1; protected static final int NO_CLIP_FLAG = 2; + private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); /** Holds the blockstate information of the block in this projectile's position. */ @Nullable private BlockState inBlockState; /** Holds the information about the list of entities this projectile have pierced through. */ @Nullable private IntOpenHashSet piercedEntities; - /** Holds the information about the list of entities this projectile have pierced through and died. */ - @Nullable - private List piercingKilledEntities; /** Defines the current sound this projectile will play when it hit something */ private SoundEvent sound = this.getHitSound(); /** Defines the item stack this projectile is. Used when picking up the projectile to turn into items. */ @@ -147,6 +146,22 @@ protected void initDataTracker(Builder builder) { // /////////////// // // PROCESS METHODS // // /////////////// // + + /** + *

{@link ProjectileEntity}

+ * {@inheritDoc} + *
+ *

{@link TurretProjectileEntity}

+ *

+ * This method is called when the projectile hits an entity, handling the logic + * for hitting, damaging, and if applicable, piercing the entity. + *

+ * In this implementation, the method will handle the damage calculation, the + * critical damage multiplier, the pierce behavior, and the fire behavior similar + * to how the {@link PersistentProjectileEntity} handles it. + * + * @param entityHitResult {@inheritDoc EntityHitResult} + */ @Override protected void onEntityHit(EntityHitResult entityHitResult) { super.onEntityHit(entityHitResult); @@ -159,7 +174,7 @@ protected void onEntityHit(EntityHitResult entityHitResult) { float velocityMagnitude = (float) this.getVelocity().length(); double damage = this.getDamage(); int damageToDeal = MathHelper.ceil( - this.speedAffectDamage ? + this.speedAffectsDamage() ? MathHelper.clamp( (double) velocityMagnitude * damage, 0.0, @@ -173,10 +188,6 @@ protected void onEntityHit(EntityHitResult entityHitResult) { this.piercedEntities = new IntOpenHashSet(this.getMaxPierceLevel()); } - if (this.piercingKilledEntities == null) { - this.piercingKilledEntities = Lists.newArrayListWithCapacity(this.getMaxPierceLevel()); - } - if (this.piercedEntities.size() >= this.getPierceLevel() + 1) { this.discard(); return; @@ -213,9 +224,6 @@ protected void onEntityHit(EntityHitResult entityHitResult) { } this.onHit(livingEntity); - if (!hitEntity.isAlive() && this.piercingKilledEntities != null) { - this.piercingKilledEntities.add(livingEntity); - } } this.playSound(this.sound , 1f, 1.2f / (this.random.nextFloat() * 0.2f + 0.9f)); @@ -359,10 +367,6 @@ protected void age() { } protected void clearPiercingStatus() { - if (this.piercingKilledEntities != null) { - this.piercingKilledEntities.clear(); - } - if (this.piercedEntities != null) { this.piercedEntities.clear(); } @@ -584,6 +588,22 @@ public void setSound(SoundEvent sound) { this.sound = sound; } + /** + * Determines whether the speed of the projectile affects the damage it deals, + * similar to how {@link PersistentProjectileEntity} handles it. + * + * @return {@code true} if the speed affects the damage, {@code false} otherwise + * + * @see #speedAffectDamage + */ + public boolean speedAffectsDamage() { + return this.speedAffectDamage; + } + + protected void setSpeedAffectsDamage(boolean speedAffectDamage) { + this.speedAffectDamage = speedAffectDamage; + } + @Override protected double getGravity() { return 0.05; @@ -757,6 +777,26 @@ protected boolean deflectsAgainstWorldBorder() { return true; } + @Override + public void setOwner(Entity owner) { + super.setOwner(owner); + + if (owner instanceof TurretEntity turret) { + this.setDamage(turret.getProjectileDamage()); + this.setPierceLevel(turret.getProjectilePierceLevel()); + } + } + + // ///////////////////////// // + // INTERFACE IMPLEMENTATIONS // + // ///////////////////////// // + + // GeoEntity // + @Override + public AnimatableInstanceCache getAnimatableInstanceCache() { + return this.geoCache; + } + // ///////////////////// // // LOCAL CLASS AND ENUMS // // ///////////////////// // diff --git a/src/main/java/com/virus5600/defensive_measures/entity/projectiles/interfaces/ExplosiveProjectile.java b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/interfaces/ExplosiveProjectile.java new file mode 100644 index 0000000..1c1bb28 --- /dev/null +++ b/src/main/java/com/virus5600/defensive_measures/entity/projectiles/interfaces/ExplosiveProjectile.java @@ -0,0 +1,4 @@ +package com.virus5600.defensive_measures.entity.projectiles.interfaces; + +public interface ExplosiveProjectile { +} diff --git a/src/main/java/com/virus5600/defensive_measures/entity/turrets/BallistaTurretEntity.java b/src/main/java/com/virus5600/defensive_measures/entity/turrets/BallistaTurretEntity.java index ba427c5..76fd299 100644 --- a/src/main/java/com/virus5600/defensive_measures/entity/turrets/BallistaTurretEntity.java +++ b/src/main/java/com/virus5600/defensive_measures/entity/turrets/BallistaTurretEntity.java @@ -49,6 +49,9 @@ public class BallistaTurretEntity extends TurretEntity implements GeoEntity { private static final Map ANIMATIONS; private static final Map> OFFSETS; private static final Map HEAL_SOUNDS; + private static final double[] DAMAGE; + private static final byte[] PIERCE_LEVELS; + /** * Contains all the items that can heal this entity. @@ -98,9 +101,9 @@ public static DefaultAttributeContainer.Builder setAttributes() { return TurretEntity.setAttributes(); } - ///////////////////// + // /////////////// // // PROCESS METHODS // - ///////////////////// + // /////////////// // @Override public void shootAt(LivingEntity target, float pullProgress) { @@ -168,9 +171,9 @@ public SoundEvent getEntityRemoveSound() { return ModSoundEvents.TURRET_REMOVED_WOOD; } - /////////////////////////// + // ///////////////////// // // ANIMATION CONTROLLERS // - /////////////////////////// + // ///////////////////// // private PlayState deathController(final AnimationState event) { if (!this.isAlive() && !this.animPlayed) { @@ -211,13 +214,22 @@ public AnimatableInstanceCache getAnimatableInstanceCache() { return this.geoCache; } - ////////////////////////////// + // //////////////////////// // // ABSTRACT IMPLEMENTATIONS // - ////////////////////////////// + // //////////////////////// // + // TurretEntity // protected List getTurretProjectileSpawn() { return OFFSETS.get(Offsets.BOLT_HOLDER); } + public double getProjectileDamage() { + return BallistaTurretEntity.DAMAGE[this.getLevel() - 1]; + } + + public byte getProjectilePierceLevel() { + return BallistaTurretEntity.PIERCE_LEVELS[this.getLevel() - 1]; + } + ///////////////////////// // LOCAL CLASSES/ENUMS // ///////////////////////// @@ -230,6 +242,18 @@ public enum Offsets { /////////////////////// static { + DAMAGE = new double[] { + 3.5, + 7.0, + 12.0 + }; + + PIERCE_LEVELS = new byte[] { + 5, + 7, + 10 + }; + OFFSETS = Map.of( Offsets.BOLT_HOLDER, List.of( new Vec3d(0, 0, 0.875) diff --git a/src/main/java/com/virus5600/defensive_measures/entity/turrets/CannonTurretEntity.java b/src/main/java/com/virus5600/defensive_measures/entity/turrets/CannonTurretEntity.java index c03e432..0b1e708 100644 --- a/src/main/java/com/virus5600/defensive_measures/entity/turrets/CannonTurretEntity.java +++ b/src/main/java/com/virus5600/defensive_measures/entity/turrets/CannonTurretEntity.java @@ -56,6 +56,7 @@ public class CannonTurretEntity extends TurretEntity implements GeoEntity { private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); private boolean animPlayed = false; + private static final double[] DAMAGE; ////////////////// // CONSTRUCTORS // @@ -91,6 +92,7 @@ public static DefaultAttributeContainer.Builder setAttributes() { TurretEntity.setTurretMaxHealth(50); return TurretEntity.setAttributes() + .add(EntityAttributes.FOLLOW_RANGE, 24) .add(EntityAttributes.ARMOR, 3) .add(EntityAttributes.ARMOR_TOUGHNESS, 2); } @@ -231,13 +233,22 @@ public AnimatableInstanceCache getAnimatableInstanceCache() { return this.geoCache; } - ////////////////////////////// + // //////////////////////// // // ABSTRACT IMPLEMENTATIONS // - ////////////////////////////// + // //////////////////////// // + // TurretEntity // protected List getTurretProjectileSpawn() { return OFFSETS.get(Offsets.BARREL); } + public double getProjectileDamage() { + return CannonTurretEntity.DAMAGE[this.getLevel() - 1]; + } + + public byte getProjectilePierceLevel() { + return 0; + } + ///////////////////////// // LOCAL CLASSES/ENUMS // ///////////////////////// @@ -250,6 +261,10 @@ public enum Offsets { /////////////////////// static { + DAMAGE = new double[] { + 10.0 + }; + OFFSETS = Map.of( Offsets.BARREL, List.of( new Vec3d(0, 0, 0.875) diff --git a/src/main/java/com/virus5600/defensive_measures/entity/turrets/TurretEntity.java b/src/main/java/com/virus5600/defensive_measures/entity/turrets/TurretEntity.java index b765925..0147920 100644 --- a/src/main/java/com/virus5600/defensive_measures/entity/turrets/TurretEntity.java +++ b/src/main/java/com/virus5600/defensive_measures/entity/turrets/TurretEntity.java @@ -182,7 +182,7 @@ public abstract class TurretEntity extends MobEntity implements Itemable, Ranged * Defines the level of this turret. The higher the level, the stronger * the turret. */ - protected int level; + protected int level = 1; /** * The material of this turret. */ @@ -261,7 +261,9 @@ public abstract class TurretEntity extends MobEntity implements Itemable, Ranged * @see #itemable * @see EntityType */ - public TurretEntity(EntityType entityType, World world, TurretMaterial material, EntityType projectile, Item itemable) { + public TurretEntity(EntityType entityType, World world, + TurretMaterial material, EntityType projectile, Item itemable + ) { this(entityType, world, material, itemable); this.projectile = projectile; } @@ -278,7 +280,9 @@ public TurretEntity(EntityType entityType, World world, Tur * * @see #itemable */ - public TurretEntity(EntityType entityType, World world, TurretMaterial material, Item itemable) { + public TurretEntity(EntityType entityType, World world, + TurretMaterial material, Item itemable + ) { super(entityType, world); this.material = material; @@ -450,14 +454,12 @@ protected void initDataTracker(DataTracker.Builder builder) { * @see DefaultAttributeContainer.Builder */ public static DefaultAttributeContainer.Builder setAttributes() { - DefaultAttributeContainer.Builder builder = TurretEntity.createMobAttributes() + return TurretEntity.createMobAttributes() .add(EntityAttributes.MAX_HEALTH, TurretEntity.getTurretMaxHealth()) .add(EntityAttributes.FOLLOW_RANGE, 16) .add(EntityAttributes.MOVEMENT_SPEED, 0) .add(EntityAttributes.KNOCKBACK_RESISTANCE, 1.0) .add(EntityAttributes.EXPLOSION_KNOCKBACK_RESISTANCE, 1.0); - - return builder; } @Override @@ -1207,23 +1209,77 @@ public List getMobEffect(Item item) { return this.isEffectSource(item) ? this.effectSource.get(item) : List.of(); } + /** + * Retrieves the maximum attack range of this turret. The max range is + * dependent on the {@code FOLLOW_RANGE} attribute value, allowing the + * turret to have a range that is within the bounds of its sight. + *

+ * The maximum attack range is calculated with the formula: + *

+	 * 	Math.floor(FOLLOW_RANGE) + eyeHeight
+	 * 
+ * The additional {@code eyeHeight} is added to the {@code FOLLOW_RANGE} value + * to account for the eye displacement used by the attack goal when targeting + * entities. + * + * @return {@code float} The maximum attack range of this turret. + * + * @see EntityAttributes#FOLLOW_RANGE + */ public float getMaxAttackRange() { - return 16.625f; + return (float) Math.floor( + this.getAttributes() + .getBaseValue(EntityAttributes.FOLLOW_RANGE) + ) + this.getStandingEyeHeight(); } public float getMinAttackRange() { return 0.1f; } + /** + * Retrieves the current barrel this turret will use to shoot. + * + * @param increment Whether to increment the barrel or not. + * + * @return {@code Vec3d} The current barrel to use for shooting. + */ + public Vec3d getCurrentBarrel(boolean increment) { + int currentBarrel = this.currentBarrel; + + if (increment) { + this.currentBarrel = this.currentBarrel >= this.getTurretProjectileSpawn().size() - 1 ? + 0 : this.currentBarrel + 1; + } + + return this.getTurretProjectileSpawn() + .get(currentBarrel); + } + ////////////////// // TRACKED DATA // ////////////////// + /** + * Retrieves the maximum level of this turret. + *

+ * The default maximum level is 3. This can value can be changed + * by overriding this method and returning a different value. + *

+ * This method is also used by {@link #getLevel()} and {@link #setLevel(int)} + * to clamp the level between 1 and the maximum level, ensuring that the + * level is within the bounds of the maximum level. + * + * @return {@code int} The maximum level of this turret. + */ + public int getMaxLevel() { + return 3; + } public int getLevel() { - return this.dataTracker.get(LEVEL); + return Math.clamp(this.dataTracker.get(LEVEL), 1, this.getMaxLevel()); } public void setLevel(int level) { - this.dataTracker.set(LEVEL, level); + this.dataTracker.set(LEVEL, Math.clamp(level, 1, this.getMaxLevel())); } /** @@ -1289,6 +1345,24 @@ public void shootAt(LivingEntity target, float pullProgress) { */ abstract List getTurretProjectileSpawn(); + /** + * Determines the damage this turret's projectile will deal. This only + * works for instances of {@link com.virus5600.defensive_measures.entity.projectiles.TurretProjectileEntity TurretProjectileEntity} + * or its subclasses due to the overridden methods and tailored behavior. + * + * @return {@code double} The damage this turret's projectile will deal. + */ + public abstract double getProjectileDamage(); + + /** + * Determines the knockback this turret's projectile will deal. This only + * works for instances of {@link com.virus5600.defensive_measures.entity.projectiles.TurretProjectileEntity TurretProjectileEntity} + * or its subclasses due to the overridden methods and tailored behavior. + * + * @return {@code byte} The projectile's pierce level. + */ + public abstract byte getProjectilePierceLevel(); + // OVERRIDABLES // /** * Sets the velocity of a projectile, along with the power and uncertainty of the projectile. @@ -1334,8 +1408,8 @@ protected void setProjectileVelocity(ProjectileEntity projectile, TurretProjecti projectile.setVelocity( vx, vy, vz, - velocityData.power, - velocityData.uncertainty + velocityData.getPower(), + velocityData.getUncertainty() ); } @@ -1363,20 +1437,13 @@ protected void shoot(TurretProjectileVelocity velocityData) { return; } - Vec3d pos = this.getRelativePos( - this.getTurretProjectileSpawn() - .get(this.currentBarrel++ % this.barrels) - ); + Vec3d pos = this.getRelativePos(this.getCurrentBarrel(true)); projectile.setPosition(pos); projectile.setOwner(this); this.setProjectileVelocity(projectile, velocityData); this.playSound(this.getShootSound(), 1.0f, 1.0f / (this.getRandom().nextFloat() * 0.4f + 0.8f)); this.getWorld().spawnEntity(projectile); - - // Resets the current barrel to 0 if it exceeds the number of barrels to prevent overflow - if (this.currentBarrel % this.barrels == 0) - this.currentBarrel = 0; } catch (IllegalArgumentException | SecurityException e) { DefensiveMeasures.printErr(e); } @@ -1494,6 +1561,16 @@ protected void clampHeadYaw() { * respective getters. */ public static class TurretProjectileVelocity { + /** + * Holds the data of the last target position of the projectile. This is used to hold + * the last position of the turret's target so when some information is updated, the + * projectile velocity can be recalculated. + *

+ * When the velocity is updated, this value is also updated to the target's position. If + * the velocity update does not include a target, then this will be set to {@code null}. + */ + @Nullable + private Vec3d lastTargetPos; /** The turret entity that will be shooting the projectile */ private final TurretEntity turret; /** The power of the projectile's velocity */ @@ -1504,6 +1581,8 @@ public static class TurretProjectileVelocity { private float upwardVelocityMultiplier; /** The velocity of the projectile */ private Vec3d velocity; + /** A flag that determines whether the projectile's trajectory is parabolic */ + private boolean isParabolic; /** * Initializes the velocity data of the turret's projectile. This can only be initialized @@ -1513,9 +1592,9 @@ public static class TurretProjectileVelocity { */ private TurretProjectileVelocity(TurretEntity turret) { this.turret = turret; - this.power = 1.5f; + this.power = 1f; this.uncertainty = turret.getWorld().getDifficulty().getId() * 2; - this.upwardVelocityMultiplier = 0.1f; + this.upwardVelocityMultiplier = 1f; this.velocity = Vec3d.ZERO; } @@ -1549,13 +1628,15 @@ public TurretProjectileVelocity setPower(float power) { /** * Retrieves the power of the projectile's velocity. + * The power scales by 1.5 times the value set as this + * was the most optimal value for the projectile's velocity. * * @return float The power of the projectile's velocity * * @see #power */ public float getPower() { - return this.power; + return this.power * 1.5f; } /** @@ -1599,17 +1680,25 @@ public TurretProjectileVelocity setUpwardVelocityMultiplier(float upwardVelocity /** * Retrieves the multiplier for the upward velocity of the projectile. + * The upward velocity multiplier scales by 0.1 times the value set as this + * was the most optimal value for the projectile's velocity. * * @return float The multiplier for the upward velocity of the projectile * * @see #upwardVelocityMultiplier */ public float getUpwardVelocityMultiplier() { - return this.upwardVelocityMultiplier; + return this.upwardVelocityMultiplier * 0.1f; } /** - * Sets the velocity of the projectile. + * Sets the velocity of the projectile. When this method is used to set the velocity, the + * projectile's velocity won't respect the following properties: + *
    + *
  • {@link #power Power}
  • + *
  • {@link #upwardVelocityMultiplier Upward Velocity Multiplier}
  • + *
  • {@link #isParabolic Parabolic Arc}
  • + *
* * @param velocity The velocity of the projectile * @@ -1618,12 +1707,18 @@ public float getUpwardVelocityMultiplier() { * @see #velocity */ public TurretProjectileVelocity setVelocity(Vec3d velocity) { - this.velocity = velocity; + this.calculateVelocity(velocity, false); return this; } /** - * Sets the velocity of the projectile. + * Sets the velocity of the projectile. When this method is used to set the velocity, the + * projectile's velocity won't respect the following properties: + *
    + *
  • {@link #power Power}
  • + *
  • {@link #upwardVelocityMultiplier Upward Velocity Multiplier}
  • + *
  • {@link #isParabolic Parabolic Arc}
  • + *
* * @param x The X-axis velocity of the projectile * @param y The Y-axis velocity of the projectile @@ -1645,14 +1740,29 @@ public TurretProjectileVelocity setVelocity(double x, double y, double z) { * @return {@code TurretProjectileVelocity} The instance of the {@link TurretProjectileVelocity} class */ public TurretProjectileVelocity setVelocity(LivingEntity target) { - double vx = (target.getX() - this.turret.getX()) * 1.0625; - double vy = target.getBodyY((double) 1 / 3) - this.turret.getY(); - double vz = (target.getZ() - this.turret.getZ()) * 1.0625; - double variance = Math.sqrt(vx * vx + vz * vz); + return this.setVelocityFromPos( + target.getX(), + target.getBodyY((double) 1 / 3), + target.getZ() + ); + } - vy += variance * this.upwardVelocityMultiplier; + /** + * Sets the velocity of the projectile based from the target position given. + * + * @param pos The target position of the projectile + * + * @return {@code TurretProjectileVelocity} The instance of the {@link TurretProjectileVelocity} class + * + * @see #velocity + */ + public TurretProjectileVelocity setVelocityFromPos(Vec3d pos) { + this.calculateVelocity(pos, true); + return this; + } - return this.setVelocity(vx, vy, vz); + public TurretProjectileVelocity setVelocityFromPos(double x, double y, double z) { + return this.setVelocityFromPos(new Vec3d(x, y, z)); } /** @@ -1684,6 +1794,84 @@ public TurretProjectileVelocity setDirectionalVelocity(float range) { return this.setVelocity(targetRange); } + + /** + * Sets the projectile's trajectory to be parabolic. This will make the projectile shoot in + * an arc-like trajectory, allowing for the creation of mortar-like projectiles. + * + * @param isParabolic {@code true} if the projectile's trajectory is parabolic, otherwise {@code false} + * + * @return {@code TurretProjectileVelocity} The instance of the {@link TurretProjectileVelocity} class + */ + public TurretProjectileVelocity setParabolic(boolean isParabolic) { + this.isParabolic = isParabolic; + return this; + } + + /** + * Retrieves whether the projectile's trajectory is parabolic. + * + * @return {@code boolean} {@code true} if the projectile's trajectory is parabolic, otherwise {@code false} + */ + public boolean isParabolic() { + return this.isParabolic; + } + + private void calculateVelocity(Vec3d v3d, boolean isPos) { + if (isPos) { + double vx, vy, vz; + + this.lastTargetPos = v3d; + // TODO: Properly calculate the parabolic trajectory (low priority) + // Parabolic Trajectory + if (this.isParabolic) { + double barrelY = this.turret + .getRelativePos(this.turret.getCurrentBarrel(false)) + .getY(); + + vx = (v3d.getX() - this.turret.getX()) * 1.0625; + vy = (v3d.getY() - barrelY); + vz = (v3d.getZ() - this.turret.getZ()) * 1.0625; + + double d3D = Math.sqrt(vx * vx + vy * vy + vz * vz); + + double minAngle = 45 * Math.PI / 180; + double maxAngle = 80 * Math.PI / 180; + double angleFactor = Math.clamp(d3D / (d3D + vy), 0.0, 1.0); + float theta = (float) (minAngle + angleFactor * (maxAngle - minAngle)); + double scalingFactor = Math.tan(theta); + double scalingFactorSqrd = scalingFactor * scalingFactor; + + vx = (vx / scalingFactorSqrd); + vy += (Math.abs(vy) * scalingFactor) - vy; + vz = (vz / scalingFactorSqrd); + } + // Straight Trajectory + else { + double barrelY = this.turret + .getRelativePos(this.turret.getCurrentBarrel(false)) + .getY(); + + vx = (v3d.getX() - this.turret.getX()) * 1.0625; + vy = (v3d.getY() - barrelY); + vz = (v3d.getZ() - this.turret.getZ()) * 1.0625; + + double variance = Math.sqrt(vx * vx + vz * vz); + vy += variance * this.getUpwardVelocityMultiplier(); + } + + // Applies a scale factor to make sure that when the power + // is applied, it will still be around its target's position. + Vec3d rawV = new Vec3d(vx, vy, vz); + double magnitude = rawV.length(); + double scaleFactor = this.getPower() / magnitude; + this.velocity = rawV.multiply(scaleFactor); + } + else { + this.lastTargetPos = null; + this.velocity = v3d; + } + } } /////////////////////// diff --git a/src/main/java/com/virus5600/defensive_measures/item/turrets/ballista/BallistaTurretItem.java b/src/main/java/com/virus5600/defensive_measures/item/turrets/ballista/BallistaTurretItem.java index 1e2e6d2..c409a76 100644 --- a/src/main/java/com/virus5600/defensive_measures/item/turrets/ballista/BallistaTurretItem.java +++ b/src/main/java/com/virus5600/defensive_measures/item/turrets/ballista/BallistaTurretItem.java @@ -1,6 +1,5 @@ package com.virus5600.defensive_measures.item.turrets.ballista; -import com.virus5600.defensive_measures.entity.turrets.TurretEntity; import net.minecraft.entity.EntityType; import net.minecraft.entity.mob.MobEntity; import net.minecraft.item.Item; @@ -10,6 +9,8 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Rarity; +import com.virus5600.defensive_measures.entity.turrets.BallistaTurretEntity; +import com.virus5600.defensive_measures.entity.turrets.TurretEntity; import com.virus5600.defensive_measures.item.turrets.TurretItem; import java.lang.reflect.Type; @@ -39,9 +40,10 @@ public void appendTooltip(ItemStack stack, Item.TooltipContext context, List