Skip to content

Commit

Permalink
implement a homing projectile for the dark projectile
Browse files Browse the repository at this point in the history
  • Loading branch information
Cheaterpaul committed Sep 30, 2024
1 parent 444bfac commit d51ee28
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 28 deletions.
2 changes: 1 addition & 1 deletion src/main/java/de/teamlapen/vampirism/core/ModEntities.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class ModEntities {
public static final DeferredHolder<EntityType<?>, EntityType<ConvertedSheepEntity>> CONVERTED_SHEEP = prepareEntityType("converted_sheep", () -> EntityType.Builder.of(ConvertedSheepEntity::new, MobCategory.CREATURE).sized(0.9F, 1.3F), false);
public static final DeferredHolder<EntityType<?>, EntityType<ConvertedCowEntity>> CONVERTED_COW = prepareEntityType("converted_cow", () -> EntityType.Builder.of(ConvertedCowEntity::new, MobCategory.CREATURE).sized(0.9F, 1.4F), false);
public static final DeferredHolder<EntityType<?>, EntityType<CrossbowArrowEntity>> CROSSBOW_ARROW = prepareEntityType("crossbow_arrow", () -> EntityType.Builder.<CrossbowArrowEntity>of(CrossbowArrowEntity::new, MobCategory.MISC).sized(0.5F, 0.5F), false);
public static final DeferredHolder<EntityType<?>, EntityType<DarkBloodProjectileEntity>> DARK_BLOOD_PROJECTILE = prepareEntityType("dark_blood_projectile", () -> EntityType.Builder.<DarkBloodProjectileEntity>of(DarkBloodProjectileEntity::new, MobCategory.MISC).sized(0.6F, 0.6F).fireImmune(), false);
public static final DeferredHolder<EntityType<?>, EntityType<DarkBloodProjectileEntity>> DARK_BLOOD_PROJECTILE = prepareEntityType("dark_blood_projectile", () -> EntityType.Builder.<DarkBloodProjectileEntity>of(DarkBloodProjectileEntity::new, MobCategory.MISC).sized(0.6F, 0.6F).fireImmune().clientTrackingRange(20), false);
public static final DeferredHolder<EntityType<?>, EntityType<DummyHunterTrainerEntity>> HUNTER_TRAINER_DUMMY = prepareEntityType("hunter_trainer_dummy", () -> EntityType.Builder.of(DummyHunterTrainerEntity::new, MobCategory.MISC).sized(0.6F, 1.95F), true);
public static final DeferredHolder<EntityType<?>, EntityType<AreaParticleCloudEntity>> PARTICLE_CLOUD = prepareEntityType("particle_cloud", () -> EntityType.Builder.of(AreaParticleCloudEntity::new, MobCategory.MISC).sized(6.0F, 0.5F).fireImmune(), false);
public static final DeferredHolder<EntityType<?>, EntityType<SoulOrbEntity>> SOUL_ORB = prepareEntityType("soul_orb", () -> EntityType.Builder.<SoulOrbEntity>of(SoulOrbEntity::new, MobCategory.MISC).sized(0.25F, 0.25F).fireImmune(), false);
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/de/teamlapen/vampirism/core/ModParticles.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ public static void spawnParticleClient(@NotNull Level worldIn, @NotNull Particle
* @param zOffset Used for random offset
* @param speed Direction is randomized but multiplied by x/y/zOffset
* @return Number of players this has been sent to.
* @deprecated Use {@link ServerLevel#sendParticles(ParticleOptions, double, double, double, int, double, double, double, double)} directly
*/
@Deprecated
public static int spawnParticlesServer(Level worldIn, @NotNull ParticleOptions particle, double posX, double posY, double posZ, int particleCount, double xOffset, double yOffset, double zOffset, double speed) {
assert worldIn instanceof ServerLevel : "Calling spawnParticlesServer on client side is pointless";
if (worldIn instanceof ServerLevel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import de.teamlapen.vampirism.api.util.VResourceLocation;
import de.teamlapen.vampirism.core.ModEntities;
import de.teamlapen.vampirism.core.ModParticles;
import de.teamlapen.vampirism.core.ModSounds;
import de.teamlapen.vampirism.particle.GenericParticleOptions;
import de.teamlapen.vampirism.util.DamageHandler;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
Expand All @@ -17,7 +17,6 @@
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
Expand All @@ -32,7 +31,7 @@
* <p>
* Damages directly hit entities but also has a small area of effect damage
*/
public class DarkBloodProjectileEntity extends AbstractHurtingProjectile {
public class DarkBloodProjectileEntity extends HomingProjectile {

protected float directDamage = 4;
protected float indirectDamage = 2;
Expand All @@ -46,17 +45,6 @@ public DarkBloodProjectileEntity(@NotNull EntityType<? extends DarkBloodProjecti
super(type, worldIn);
}

/**
* Copies the location from shooter.
* Adds a small random to the motion
*/
public DarkBloodProjectileEntity(@NotNull Level worldIn, @NotNull LivingEntity shooter, Vec3 accel) {
super(ModEntities.DARK_BLOOD_PROJECTILE.get(), shooter, accel, worldIn);
}

/**
* Does not add a small random to the motion
*/
public DarkBloodProjectileEntity(@NotNull Level worldIn, double x, double y, double z, Vec3 accel) {
super(ModEntities.DARK_BLOOD_PROJECTILE.get(), x, y, z, accel, worldIn);
}
Expand Down Expand Up @@ -90,9 +78,9 @@ public void explode(int distanceSq, @Nullable Entity excludeEntity) {

}
}
if (!this.level().isClientSide) {
ModParticles.spawnParticlesServer(this.level(), new GenericParticleOptions(VResourceLocation.mc("spell_1"), 7, 0xA01010, 0.2F), this.getX(), this.getY(), this.getZ(), 40, 1, 1, 1, 0);
ModParticles.spawnParticlesServer(this.level(), new GenericParticleOptions(VResourceLocation.mc("spell_6"), 10, 0x700505), this.getX(), this.getY(), this.getZ(), 15, 1, 1, 1, 0);
if (this.level() instanceof ServerLevel serverLevel) {
serverLevel.sendParticles(new GenericParticleOptions(VResourceLocation.mc("spell_1"), 7, 0xA01010, 0.2F), this.getX(), this.getY(), this.getZ(), 40, 1, 1, 1, 0);
serverLevel.sendParticles(new GenericParticleOptions(VResourceLocation.mc("spell_6"), 10, 0x700505), this.getX(), this.getY(), this.getZ(), 15, 1, 1, 1, 0);
this.level().playSound(null, getX(), getY(), getZ(), ModSounds.BLOOD_PROJECTILE_HIT.get(), SoundSource.PLAYERS, 1f, 1f);
}
this.discard();
Expand Down Expand Up @@ -153,12 +141,12 @@ public void readAdditionalSaveData(@NotNull CompoundTag compound) {
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
if (this.level() instanceof ServerLevel serverLevel) {
Vec3 center = this.position();
ModParticles.spawnParticlesClient(this.level(), new GenericParticleOptions(VResourceLocation.mc("spell_4"), 4, 0xA01010, 0f), center.x, center.y, center.z, 5, getPickRadius(), this.random);
serverLevel.sendParticles(new GenericParticleOptions(VResourceLocation.mc("spell_4"), 4, 0xA01010, 0f), center.x, center.y, center.z, 5, (getRandom().nextDouble()) * 2 - 1,(getRandom().nextDouble()) * 2 - 1,(getRandom().nextDouble()) * 2 - 1, 1);

if (this.tickCount % 3 == 0) {
ModParticles.spawnParticleClient(this.level(), new GenericParticleOptions(VResourceLocation.mc("effect_4"), 12, 0xC01010, 0.4F), center.x, center.y, center.z);
serverLevel.sendParticles(new GenericParticleOptions(VResourceLocation.mc("effect_4"), 12, 0xC01010, 0.4f), center.x, center.y, center.z, 5, 0,0,0, 1);
}
}

Expand Down
112 changes: 112 additions & 0 deletions src/main/java/de/teamlapen/vampirism/entity/HomingProjectile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package de.teamlapen.vampirism.entity;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaterniond;
import org.joml.Vector3d;

import javax.annotation.Nullable;
import java.util.UUID;

public abstract class HomingProjectile extends AbstractHurtingProjectile {

@Nullable
private UUID targetUUID;
@Nullable
private LivingEntity cachedTarget;

private Vec3 direction;

protected HomingProjectile(EntityType<? extends AbstractHurtingProjectile> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
}

public HomingProjectile(EntityType<? extends AbstractHurtingProjectile> pEntityType, double pX, double pY, double pZ, Vec3 direction, Level pLevel) {
super(pEntityType, pX, pY, pZ, direction, pLevel);
this.direction = direction;
}

public HomingProjectile(EntityType<? extends AbstractHurtingProjectile> pEntityType, LivingEntity pShooter, Vec3 offset, Level pLevel) {
super(pEntityType, pShooter, offset, pLevel);
}

public void setTarget(@Nullable LivingEntity cachedTarget) {
if (cachedTarget != null) {
this.targetUUID = cachedTarget.getUUID();
this.cachedTarget = cachedTarget;
}
}

@Override
public void addAdditionalSaveData(@NotNull CompoundTag pCompound) {
super.addAdditionalSaveData(pCompound);
if (this.targetUUID != null) {
pCompound.putUUID("target", this.targetUUID);
}
}

@Override
public void readAdditionalSaveData(@NotNull CompoundTag pCompound) {
super.readAdditionalSaveData(pCompound);
if (pCompound.hasUUID("target")) {
this.targetUUID = pCompound.getUUID("target");
}
}

@Nullable
protected LivingEntity getTarget() {
if (targetUUID != null) {
if (this.cachedTarget != null) {
if (this.cachedTarget.isRemoved()) {
this.cachedTarget = null;
} else {
return this.cachedTarget;
}
} else if (this.level() instanceof ServerLevel serverLevel) {
this.cachedTarget = (LivingEntity) serverLevel.getEntity(this.targetUUID);
return this.cachedTarget;
}
}
return null;
}

@Override
public void tick() {
super.tick();
LivingEntity target = getTarget();
if (target != null) {
Vec3 updatedTarget = target.getEyePosition();
Vec3 position = position();
Vec3 idealDirection = updatedTarget.subtract(position);
Vec3 currentDirection = this.direction;

double angleBetween = Math.acos(idealDirection.dot(currentDirection));
double maxAngle = Math.toRadians(20);

if (angleBetween > maxAngle) {
Vec3 rotationAxis = currentDirection.cross(idealDirection).normalize();

// Create a quaternion for the rotation
Quaterniond rotation = new Quaterniond().rotationAxis(maxAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z);

// Apply the rotation to the current direction
Vector3d newDir = rotation.transform(new Vector3d(currentDirection.x, currentDirection.y, currentDirection.z));

this.direction = new Vec3(newDir.x, newDir.y, newDir.z);
} else {
this.direction = idealDirection;
}

// use the acceleration from the superclass
double length = getDeltaMovement().length();

this.setDeltaMovement(this.direction.normalize().scale(length));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import de.teamlapen.vampirism.core.ModRefinements;
import de.teamlapen.vampirism.entity.DarkBloodProjectileEntity;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -54,12 +58,17 @@ protected IActionResult activate(@NotNull IVampirePlayer player, ActivationConte
if (skillHandler.isRefinementEquipped(ModRefinements.DARK_BLOOD_PROJECTILE_SPEED)) {
speed = 1.4f;
}
Vec3 startPosition = player.asEntity().getEyePosition();
Vec3 direction = player.asEntity().getViewVector(1.0F);
Vec3 endPosition = startPosition.add(direction.x * 200.0, direction.y * 200.0, direction.z * 200.0);
EntityHitResult entityHitResult = ProjectileUtil.getEntityHitResult(player.asEntity().level(), player.asEntity(), startPosition, endPosition, new AABB(startPosition, endPosition), (e) -> e instanceof LivingEntity && !e.isSpectator(), 0);
LivingEntity target = entityHitResult != null && entityHitResult.getEntity() instanceof LivingEntity livingEntity ? livingEntity : null;

if (skillHandler.isRefinementEquipped(ModRefinements.DARK_BLOOD_PROJECTILE_AOE)) {
for (int i = 0; i < 32; i++) {
Vec3 vec3d = getRotationVector(shooter.getViewXRot(1.0f), shooter.getViewYRot(1.0f) + i * 11.25f);
DarkBloodProjectileEntity entity = createProjectile(shooter, shooter.position(), 0, vec3d, false, 0, 0, 0.95f);
entity.setMaxTicks(7);
DarkBloodProjectileEntity entity = createProjectile(shooter, shooter.position(), vec3d, false, 0, 0, speed, null);
entity.setMaxTicks(15);
entity.excludeShooter();
if (i == 0) {
entity.setDamage(0, directDamage);
Expand All @@ -68,20 +77,21 @@ protected IActionResult activate(@NotNull IVampirePlayer player, ActivationConte
}
} else {
boolean goThrough = skillHandler.isRefinementEquipped(ModRefinements.DARK_BLOOD_PROJECTILE_PENETRATION);
createProjectile(shooter, shooter.position(), shooter.getEyeHeight() * 0.9f, shooter.getViewVector(1.0F), goThrough, directDamage, indirectDamage, speed);
createProjectile(shooter, shooter.getEyePosition(), shooter.getViewVector(1.0F), goThrough, directDamage, indirectDamage, speed, target);
if (skillHandler.isRefinementEquipped(ModRefinements.DARK_BLOOD_PROJECTILE_MULTI_SHOT)) {
createProjectile(shooter, shooter.position(), shooter.getEyeHeight() * 0.9f, getVectorForRotation(shooter.getViewXRot(1.0f), shooter.getViewYRot(1.0f) + 30f), goThrough, directDamage, indirectDamage, speed);
createProjectile(shooter, shooter.position(), shooter.getEyeHeight() * 0.9f, getVectorForRotation(shooter.getViewXRot(1.0f), shooter.getViewYRot(1.0f) - 30f), goThrough, directDamage, indirectDamage, speed);
createProjectile(shooter, shooter.getEyePosition(), getVectorForRotation(shooter.getViewXRot(1.0f), shooter.getViewYRot(1.0f) + 30f), goThrough, directDamage, indirectDamage, speed, target);
createProjectile(shooter, shooter.getEyePosition(), getVectorForRotation(shooter.getViewXRot(1.0f), shooter.getViewYRot(1.0f) - 30f), goThrough, directDamage, indirectDamage, speed, target);
}
}
return IActionResult.SUCCESS;
}

private @NotNull DarkBloodProjectileEntity createProjectile(@NotNull Player shooter, @NotNull Vec3 position, double height, @NotNull Vec3 direction, boolean goThrough, float directDamage, float indirectDamage, float speed) {
DarkBloodProjectileEntity entity = new DarkBloodProjectileEntity(shooter.getCommandSenderWorld(), position.x + direction.x, position.y + height, position.z + direction.z, direction);
private @NotNull DarkBloodProjectileEntity createProjectile(@NotNull Player shooter, @NotNull Vec3 position, @NotNull Vec3 direction, boolean goThrough, float directDamage, float indirectDamage, float speed, LivingEntity target) {
DarkBloodProjectileEntity entity = new DarkBloodProjectileEntity(shooter.getCommandSenderWorld(), position.x + direction.x, position.y + direction.y, position.z + direction.z, direction);
entity.setMotionFactor(speed);
entity.setOwner(shooter);
entity.setDamage(directDamage, indirectDamage);
entity.setTarget(target);
if (goThrough) {
entity.setGothrough(true);
}
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/accesstransformer.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ protected net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal crossbowSt
protected net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal canRun()Z
protected net.minecraft.world.inventory.AbstractContainerMenu remoteSlots
protected net.minecraft.world.inventory.AbstractFurnaceMenu data
protected net.minecraft.world.entity.projectile.AbstractHurtingProjectile assignPower(DDD)V
public net.minecraft.world.item.enchantment.EnchantmentHelper runIterationOnItem(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/enchantment/EnchantmentHelper$EnchantmentVisitor;)V
public net.minecraft.world.item.enchantment.Enchantment modifyItemFilteredCount(Lnet/minecraft/core/component/DataComponentType;Lnet/minecraft/server/level/ServerLevel;ILnet/minecraft/world/item/ItemStack;Lorg/apache/commons/lang3/mutable/MutableFloat;)V
public net.minecraft.data.models.BlockModelGenerators createBooleanModelDispatch(Lnet/minecraft/world/level/block/state/properties/BooleanProperty;Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/data/models/blockstates/PropertyDispatch;
Expand Down

0 comments on commit d51ee28

Please sign in to comment.