beamaroni

This commit is contained in:
FatherToast 2022-08-28 11:25:08 -05:00
parent a4652d2e39
commit 5bae76072f
10 changed files with 323 additions and 293 deletions

View file

@ -4,6 +4,7 @@ import fathertoast.specialmobs.client.renderer.entity.family.SpecialCreeperRende
import fathertoast.specialmobs.common.entity.creeper.EnderCreeperEntity; import fathertoast.specialmobs.common.entity.creeper.EnderCreeperEntity;
import net.minecraft.client.renderer.entity.EntityRendererManager; import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.entity.monster.CreeperEntity; import net.minecraft.entity.monster.CreeperEntity;
import net.minecraft.entity.monster.EndermanEntity;
import net.minecraft.util.math.vector.Vector3d; import net.minecraft.util.math.vector.Vector3d;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
@ -18,6 +19,7 @@ public class EnderCreeperRenderer extends SpecialCreeperRenderer {
public EnderCreeperRenderer( EntityRendererManager rendererManager ) { super( rendererManager ); } public EnderCreeperRenderer( EntityRendererManager rendererManager ) { super( rendererManager ); }
/** {@link net.minecraft.client.renderer.entity.EndermanRenderer#getRenderOffset(EndermanEntity, float)} */
@Override @Override
public Vector3d getRenderOffset( CreeperEntity entity, float partialTicks ) { public Vector3d getRenderOffset( CreeperEntity entity, float partialTicks ) {
if( ((EnderCreeperEntity) entity).isCreepy() ) { if( ((EnderCreeperEntity) entity).isCreepy() ) {
@ -26,5 +28,4 @@ public class EnderCreeperRenderer extends SpecialCreeperRenderer {
} }
return super.getRenderOffset( entity, partialTicks ); return super.getRenderOffset( entity, partialTicks );
} }
} }

View file

@ -1,118 +0,0 @@
package fathertoast.specialmobs.client.renderer.entity.species;
import fathertoast.specialmobs.common.entity.enderman.RunicEndermanEntity;
import net.minecraft.client.renderer.entity.model.BipedModel;
import net.minecraft.client.renderer.model.ModelRenderer;
/** Epic copy-paste of {@link net.minecraft.client.renderer.entity.model.EndermanModel} */
public class RunicEndermanModel<T extends RunicEndermanEntity> extends BipedModel<T> {
public boolean carrying;
public boolean creepy;
public RunicEndermanModel(float something) {
super(0.0F, -14.0F, 64, 32);
float f = -14.0F;
this.hat = new ModelRenderer(this, 0, 16);
this.hat.addBox(-4.0F, -8.0F, -4.0F, 8.0F, 8.0F, 8.0F, something - 0.5F);
this.hat.setPos(0.0F, -14.0F, 0.0F);
this.body = new ModelRenderer(this, 32, 16);
this.body.addBox(-4.0F, 0.0F, -2.0F, 8.0F, 12.0F, 4.0F, something);
this.body.setPos(0.0F, -14.0F, 0.0F);
this.rightArm = new ModelRenderer(this, 56, 0);
this.rightArm.addBox(-1.0F, -2.0F, -1.0F, 2.0F, 30.0F, 2.0F, something);
this.rightArm.setPos(-3.0F, -12.0F, 0.0F);
this.leftArm = new ModelRenderer(this, 56, 0);
this.leftArm.mirror = true;
this.leftArm.addBox(-1.0F, -2.0F, -1.0F, 2.0F, 30.0F, 2.0F, something);
this.leftArm.setPos(5.0F, -12.0F, 0.0F);
this.rightLeg = new ModelRenderer(this, 56, 0);
this.rightLeg.addBox(-1.0F, 0.0F, -1.0F, 2.0F, 30.0F, 2.0F, something);
this.rightLeg.setPos(-2.0F, -2.0F, 0.0F);
this.leftLeg = new ModelRenderer(this, 56, 0);
this.leftLeg.mirror = true;
this.leftLeg.addBox(-1.0F, 0.0F, -1.0F, 2.0F, 30.0F, 2.0F, something);
this.leftLeg.setPos(2.0F, -2.0F, 0.0F);
}
@Override
public void setupAnim(T enderman, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
super.setupAnim(enderman, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
this.head.visible = true;
this.body.xRot = 0.0F;
this.body.y = -14.0F;
this.body.z = -0.0F;
this.rightLeg.xRot -= 0.0F;
this.leftLeg.xRot -= 0.0F;
this.rightArm.xRot = (float)((double)this.rightArm.xRot * 0.5D);
this.leftArm.xRot = (float)((double)this.leftArm.xRot * 0.5D);
this.rightLeg.xRot = (float)((double)this.rightLeg.xRot * 0.5D);
this.leftLeg.xRot = (float)((double)this.leftLeg.xRot * 0.5D);
if (this.rightArm.xRot > 0.4F) {
this.rightArm.xRot = 0.4F;
}
if (this.leftArm.xRot > 0.4F) {
this.leftArm.xRot = 0.4F;
}
if (this.rightArm.xRot < -0.4F) {
this.rightArm.xRot = -0.4F;
}
if (this.leftArm.xRot < -0.4F) {
this.leftArm.xRot = -0.4F;
}
if (this.rightLeg.xRot > 0.4F) {
this.rightLeg.xRot = 0.4F;
}
if (this.leftLeg.xRot > 0.4F) {
this.leftLeg.xRot = 0.4F;
}
if (this.rightLeg.xRot < -0.4F) {
this.rightLeg.xRot = -0.4F;
}
if (this.leftLeg.xRot < -0.4F) {
this.leftLeg.xRot = -0.4F;
}
if (this.carrying) {
this.rightArm.xRot = -0.5F;
this.leftArm.xRot = -0.5F;
this.rightArm.zRot = 0.05F;
this.leftArm.zRot = -0.05F;
}
this.rightArm.z = 0.0F;
this.leftArm.z = 0.0F;
this.rightLeg.z = 0.0F;
this.leftLeg.z = 0.0F;
this.rightLeg.y = -5.0F;
this.leftLeg.y = -5.0F;
this.head.z = -0.0F;
this.head.y = -13.0F;
this.hat.x = this.head.x;
this.hat.y = this.head.y;
this.hat.z = this.head.z;
this.hat.xRot = this.head.xRot;
this.hat.yRot = this.head.yRot;
this.hat.zRot = this.head.zRot;
if (this.creepy) {
this.head.y -= 5.0F;
}
this.rightArm.setPos(-5.0F, -12.0F, 0.0F);
this.leftArm.setPos(5.0F, -12.0F, 0.0F);
if (enderman.getBeamTargetId().isPresent()) {
leftArm.xRot = -0.5F;
rightArm.xRot = -0.5F;
leftArm.zRot = -0.25F;
rightArm.zRot = 0.25F;
}
}
}

View file

@ -2,107 +2,146 @@ package fathertoast.specialmobs.client.renderer.entity.species;
import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder; import com.mojang.blaze3d.vertex.IVertexBuilder;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer; import fathertoast.specialmobs.client.renderer.entity.family.SpecialEndermanRenderer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.enderman.RunicEndermanEntity; import fathertoast.specialmobs.common.entity.enderman.RunicEndermanEntity;
import net.minecraft.client.renderer.IRenderTypeBuffer; import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EnderDragonRenderer; import net.minecraft.client.renderer.entity.EnderDragonRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager; import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.MobRenderer; import net.minecraft.client.renderer.entity.model.EndermanModel;
import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.entity.Entity; import net.minecraft.entity.monster.EndermanEntity;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.vector.Matrix3f; import net.minecraft.util.math.vector.Matrix3f;
import net.minecraft.util.math.vector.Matrix4f; import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f; import net.minecraft.util.math.vector.Vector3f;
public class RunicEndermanRenderer extends MobRenderer<RunicEndermanEntity, RunicEndermanModel<RunicEndermanEntity>> { public class RunicEndermanRenderer extends SpecialEndermanRenderer {
public static final ResourceLocation BEAM_TEXTURE_LOCATION = new ResourceLocation( "textures/entity/end_crystal/end_crystal_beam.png" );
public static final ResourceLocation CRYSTAL_BEAM_LOCATION = new ResourceLocation("textures/entity/end_crystal/end_crystal_beam.png"); private static final RenderType BEAM = RenderType.entitySmoothCutout( BEAM_TEXTURE_LOCATION );
private static final RenderType BEAM = RenderType.entitySmoothCutout(CRYSTAL_BEAM_LOCATION);
public RunicEndermanRenderer( EntityRendererManager rendererManager ) { super( rendererManager ); }
private final float baseShadowRadius;
public RunicEndermanRenderer(EntityRendererManager rendererManager) {
super(rendererManager, new RunicEndermanModel<>(0.0F), 0.5F);
baseShadowRadius = shadowRadius;
addLayer( new SpecialMobEyesLayer<>( this ) );
addLayer( new SpecialMobOverlayLayer<>( this, new RunicEndermanModel<>( 0.25F ) ) );
}
@Override @Override
public void render(RunicEndermanEntity enderman, float rotation, float partialTicks, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight) { public void render( EndermanEntity entity, float rotation, float partialTicks,
if (enderman.getBeamTargetId().isPresent()) { MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight ) {
Entity entity = enderman.level.getEntity(enderman.getBeamTargetId().getAsInt()); // Beam attack
final RunicEndermanEntity.BeamState beamState = ((RunicEndermanEntity) entity).getBeamState();
if (entity != null) { if( beamState != RunicEndermanEntity.BeamState.OFF ) {
matrixStack.pushPose(); matrixStack.pushPose();
float x = (float)(entity.getX() - MathHelper.lerp(partialTicks, enderman.xo, enderman.getX()));
float y = (float)(entity.getY() - MathHelper.lerp(partialTicks, enderman.yo, enderman.getY())); Vector3d beamVec = entity.getViewVector( partialTicks ).scale( RunicEndermanEntity.BEAM_MAX_RANGE );
float z = (float)(entity.getZ() - MathHelper.lerp(partialTicks, enderman.zo, enderman.getZ()));
renderBeamAttack(x, y, z, partialTicks, enderman.tickCount, matrixStack, buffer, packedLight); final Vector3d beamStartPos = entity.getEyePosition( partialTicks );
matrixStack.popPose(); final Vector3d beamEndPos = beamStartPos.add( beamVec );
final RayTraceResult blockRayTrace = entity.level.clip(
new RayTraceContext( beamStartPos, beamEndPos,
RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.NONE, entity ) );
if( blockRayTrace.getType() != RayTraceResult.Type.MISS ) {
beamVec = blockRayTrace.getLocation().subtract( beamStartPos );
} }
renderBeamAttack( beamState, entity.getEyeHeight(), (float) beamVec.x, (float) beamVec.y, (float) beamVec.z,
partialTicks, entity.tickCount, matrixStack, buffer, packedLight );
matrixStack.popPose();
} }
super.render(enderman, rotation, partialTicks, matrixStack, buffer, packedLight); super.render( entity, rotation, partialTicks, matrixStack, buffer, packedLight );
} }
@Override @Override
public ResourceLocation getTextureLocation(RunicEndermanEntity enderman) { protected boolean isBodyVisible( EndermanEntity entity ) {
return ((ISpecialMob<?>) enderman).getSpecialData().getTexture(); // This is called right after EntityModel#setupAnim, so we add on our own animation here as a hacky way to avoid a lot of extra stuff
final RunicEndermanEntity.BeamState beamState = ((RunicEndermanEntity) entity).getBeamState();
if( beamState != RunicEndermanEntity.BeamState.OFF ) {
final float zRot = beamState == RunicEndermanEntity.BeamState.DAMAGING ? 0.25F : 0.6F;
final EndermanModel<EndermanEntity> model = getModel();
model.leftArm.xRot = -0.6F;
model.rightArm.xRot = -0.6F;
model.leftArm.zRot = -zRot;
model.rightArm.zRot = zRot;
}
return super.isBodyVisible( entity );
} }
@Override /**
protected void scale(RunicEndermanEntity entity, MatrixStack matrixStack, float partialTick ) { * Copy-paste Ender Crystal render code.
super.scale( entity, matrixStack, partialTick ); * {@link EnderDragonRenderer#renderCrystalBeams(float, float, float, float, int, MatrixStack, IRenderTypeBuffer, int)}
*/
final float scale = ((ISpecialMob<?>) entity).getSpecialData().getRenderScale(); private void renderBeamAttack( RunicEndermanEntity.BeamState beamState, float offsetY, float dX, float dY, float dZ,
shadowRadius = baseShadowRadius * scale; float partialTicks, int tickCount, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight ) {
matrixStack.scale( scale, scale, scale );
}
/** Copy-paste Ender Crystal render code. {@link EnderDragonRenderer#renderCrystalBeams(float, float, float, float, int, MatrixStack, IRenderTypeBuffer, int)} */
private void renderBeamAttack(float x, float y, float z, float partialTicks, int tickCount, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight) {
float f = MathHelper.sqrt(x * x + z * z);
float xyzMul = x * x + y * y + z * z;
float f1 = MathHelper.sqrt(xyzMul);
matrixStack.pushPose(); matrixStack.pushPose();
matrixStack.translate(0.0D, 2.0D, 0.0D);
matrixStack.mulPose(Vector3f.YP.rotation((float)(-Math.atan2(z, x)) - ((float)Math.PI / 2F))); final float dH = MathHelper.sqrt( dX * dX + dZ * dZ );
matrixStack.mulPose(Vector3f.XP.rotation((float)(-Math.atan2(f, y)) - ((float)Math.PI / 2F))); final float length = MathHelper.sqrt( dX * dX + dY * dY + dZ * dZ );
IVertexBuilder ivertexbuilder = buffer.getBuffer(BEAM); matrixStack.translate( 0.0, offsetY, 0.0 );
matrixStack.mulPose( Vector3f.YP.rotation( (float) -Math.atan2( dZ, dX ) - (float) Math.PI / 2.0F ) );
float f2 = 0.0F - ((float)tickCount + partialTicks) * 0.01F; matrixStack.mulPose( Vector3f.XP.rotation( (float) -Math.atan2( dH, dY ) - (float) Math.PI / 2.0F ) );
float f3 = MathHelper.sqrt(xyzMul) / 32.0F - ((float)tickCount + partialTicks) * 0.01F;
float f4 = 0.0F; final IVertexBuilder vertexBuilder = buffer.getBuffer( BEAM );
float f5 = 0.75F; final MatrixStack.Entry matrixEntry = matrixStack.last();
float f6 = 0.0F; final Matrix4f pose = matrixEntry.pose();
final Matrix3f normal = matrixEntry.normal();
MatrixStack.Entry entry = matrixStack.last();
Matrix4f matrix4f = entry.pose(); final int eRB;
Matrix3f matrix3f = entry.normal(); final int alpha;
final float eScale;
for(int j = 1; j <= 8; ++j) { final float v1;
float f7 = MathHelper.sin((float)j * ((float)Math.PI * 2F) / 8.0F) * 0.75F; if( beamState == RunicEndermanEntity.BeamState.DAMAGING ) {
float f8 = MathHelper.cos((float)j * ((float)Math.PI * 2F) / 8.0F) * 0.75F; eRB = 255;
float f9 = (float)j / 8.0F; alpha = 255;
eScale = 1.0F;
ivertexbuilder.vertex(matrix4f, f4 * 0.2F, f5 * 0.2F, 0.0F).color(0, 0, 0, 255).uv(f6, f2).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(matrix3f, 0.0F, -1.0F, 0.0F).endVertex(); v1 = (tickCount + partialTicks) * -0.01F;
ivertexbuilder.vertex(matrix4f, f4, f5, f1).color(255, 100, 255, 255).uv(f6, f3).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(matrix3f, 0.0F, -1.0F, 0.0F).endVertex();
ivertexbuilder.vertex(matrix4f, f7, f8, f1).color(255, 100, 255, 255).uv(f9, f3).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(matrix3f, 0.0F, -1.0F, 0.0F).endVertex();
ivertexbuilder.vertex(matrix4f, f7 * 0.2F, f8 * 0.2F, 0.0F).color(0, 0, 0, 255).uv(f9, f2).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(matrix3f, 0.0F, -1.0F, 0.0F).endVertex();
f4 = f7;
f5 = f8;
f6 = f9;
} }
else {
eRB = 100;
alpha = 170;
eScale = 0.2F;
v1 = (tickCount + partialTicks) * 0.006F;
}
final float v2 = length / 32.0F + v1;
float u1 = 0.0F;
float x1 = 0.0F;
float y1 = 0.75F;
final int resolution = 8;
for( int n = 1; n <= resolution; n++ ) {
final float u2 = (float) n / resolution;
final float angle = u2 * (float) Math.PI * 2.0F;
final float x2 = MathHelper.sin( angle ) * 0.75F;
final float y2 = MathHelper.cos( angle ) * 0.75F;
vertexBuilder.vertex( pose, x1 * 0.2F, y1 * 0.2F, 0.0F )
.color( 0, 0, 0, alpha )
.uv( u1, v1 ).overlayCoords( OverlayTexture.NO_OVERLAY ).uv2( packedLight )
.normal( normal, 0.0F, -1.0F, 0.0F ).endVertex();
vertexBuilder.vertex( pose, x1 * eScale, y1 * eScale, length )
.color( eRB, 100, eRB, alpha )
.uv( u1, v2 ).overlayCoords( OverlayTexture.NO_OVERLAY ).uv2( packedLight )
.normal( normal, 0.0F, -1.0F, 0.0F ).endVertex();
vertexBuilder.vertex( pose, x2 * eScale, y2 * eScale, length )
.color( eRB, 100, eRB, alpha )
.uv( u2, v2 ).overlayCoords( OverlayTexture.NO_OVERLAY ).uv2( packedLight )
.normal( normal, 0.0F, -1.0F, 0.0F ).endVertex();
vertexBuilder.vertex( pose, x2 * 0.2F, y2 * 0.2F, 0.0F )
.color( 0, 0, 0, alpha )
.uv( u2, v1 ).overlayCoords( OverlayTexture.NO_OVERLAY ).uv2( packedLight )
.normal( normal, 0.0F, -1.0F, 0.0F ).endVertex();
x1 = x2;
y1 = y2;
u1 = u2;
}
matrixStack.popPose(); matrixStack.popPose();
} }
} }

View file

@ -276,12 +276,8 @@ public class BestiaryInfo {
immuneToStickyBlocks.addAll( parent.immuneToStickyBlocks.getEntries() ); immuneToStickyBlocks.addAll( parent.immuneToStickyBlocks.getEntries() );
immuneToPotions.addAll( parent.immuneToPotions.getEntries() ); immuneToPotions.addAll( parent.immuneToPotions.getEntries() );
rangedAttackDamage = parent.rangedAttackDamage; setAllRangedStats( parent.rangedAttackDamage, parent.rangedAttackSpread, parent.rangedWalkSpeed,
rangedAttackSpread = parent.rangedAttackSpread; parent.rangedAttackCooldown, parent.rangedAttackMaxCooldown, parent.rangedAttackMaxRange );
rangedWalkSpeed = parent.rangedWalkSpeed;
rangedAttackCooldown = parent.rangedAttackCooldown;
rangedAttackMaxCooldown = parent.rangedAttackMaxCooldown;
rangedAttackMaxRange = parent.rangedAttackMaxRange;
} }
} }
@ -593,18 +589,14 @@ public class BestiaryInfo {
.multiplyRangedCooldown( cooldown ).multiplyRangedMaxCooldown( cooldown ).multiplyRangedMaxRange( range ); .multiplyRangedCooldown( cooldown ).multiplyRangedMaxCooldown( cooldown ).multiplyRangedMaxRange( range );
} }
/** Converts the entity to a fishing rod user by disabling unused ranged attack stats (for a bow user). */ /** Converts the entity's ranged attack stats to those of a fishing rod user. */
public Builder convertBowToFishing() { return rangedDamage( -1.0 ).rangedWalkSpeed( -1.0 ); } public Builder convertRangedAttackToFishing( double spread, int cooldown, double range ) {
return setAllRangedStats( -1.0, spread, -1.0, cooldown, -1, range );
}
/** Converts the entity to a fishing rod user by disabling unused ranged attack stats (for a throwing item user). */ /** Converts the entity's ranged attack stats to those of a beam attack user. */
public Builder convertThrowToFishing() { return rangedWalkSpeed( -1.0 ); } public Builder convertRangedAttackToBeam( double damage, double turnSpeed, int charge, int duration, double range ) {
return setAllRangedStats( damage, -1.0, turnSpeed, charge, charge + duration, range );
/** Converts the entity to a fishing rod user by disabling unused ranged attack stats (for a spit shooter). */
public Builder convertSpitToFishing() { return rangedDamage( -1.0 ).rangedMaxCooldown( -1 ); }
/** Sets the species fishing rod user stats. */
public Builder fishingAttack( double spread, int cooldown, double range ) {
return rangedSpread( spread ).rangedCooldown( cooldown ).rangedMaxRange( range );
} }
/** Sets the species as unable to use ranged attacks (for any ranged user). */ /** Sets the species as unable to use ranged attacks (for any ranged user). */
@ -679,6 +671,17 @@ public class BestiaryInfo {
return this; return this;
} }
/** Converts ALL the entity's ranged attack stats. This method allows enabling/disabling stats on variants. */
private Builder setAllRangedStats( double damage, double spread, double walkSpeed, int cooldown, int maxCooldown, double range ) {
rangedAttackDamage = damage;
rangedAttackSpread = spread;
rangedWalkSpeed = walkSpeed;
rangedAttackCooldown = cooldown;
rangedAttackMaxCooldown = maxCooldown;
rangedAttackMaxRange = range;
return this;
}
//--------------- Attribute Changes ---------------- //--------------- Attribute Changes ----------------

View file

@ -1,68 +1,159 @@
package fathertoast.specialmobs.common.entity.ai.goal; package fathertoast.specialmobs.common.entity.ai.goal;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.enderman.RunicEndermanEntity; import fathertoast.specialmobs.common.entity.enderman.RunicEndermanEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.goal.Goal; import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.util.DamageSource; import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
public class RunicEndermanBeamAttackGoal extends Goal { public class RunicEndermanBeamAttackGoal extends Goal {
private final RunicEndermanEntity mob;
private final RunicEndermanEntity runicEnderman; private final DamageSource beamDamageSource;
private final int maxAttackTime;
private final double baseDamage; private Vector3d targetPos;
private int attackTime; private int attackTime;
public RunicEndermanBeamAttackGoal(RunicEndermanEntity runicEnderman, int maxAttackTime, double baseDamage ) { public RunicEndermanBeamAttackGoal( RunicEndermanEntity entity ) {
this.runicEnderman = runicEnderman; mob = entity;
this.maxAttackTime = maxAttackTime; beamDamageSource = new EntityDamageSource( DamageSource.MAGIC.getMsgId(), entity );
this.baseDamage = baseDamage; setFlags( EnumSet.of( Goal.Flag.MOVE, Flag.LOOK ) );
} }
/** @return Returns true if this AI can be activated. */
@Override @Override
public boolean canUse() { public boolean canUse() {
if (runicEnderman.isVehicle()) if( mob.isVehicle() ) return false;
return false;
final LivingEntity target = mob.getTarget();
LivingEntity target = runicEnderman.getTarget(); if( target != null && target.isAlive() && mob.getSensing().canSee( target ) &&
mob.tickCount % 8 == 0 && mob.getRandom().nextInt( 10 ) == 0 && target.distanceToSqr( mob ) <=
return target != null mob.getSpecialData().getRangedAttackMaxRange() * mob.getSpecialData().getRangedAttackMaxRange() ) {
&& target.isAlive() setTargetPos( target );
&& runicEnderman.getSensing().canSee(target) attackTime = 0;
&& (runicEnderman.tickCount % 8 == 0 && runicEnderman.getRandom().nextInt(10) == 0); mob.setBeamState( RunicEndermanEntity.BeamState.CHARGING );
return true;
}
return false;
} }
/** @return Called each update while active and returns true if this AI can remain active. */
@Override @Override
public boolean canContinueToUse() { public boolean canContinueToUse() {
LivingEntity target = runicEnderman.getTarget(); final LivingEntity target = mob.getTarget();
return (mob.getBeamState() == RunicEndermanEntity.BeamState.DAMAGING || target != null && target.isAlive()) &&
return attackTime > 0 && target != null && target.isAlive() && runicEnderman.getSensing().canSee(target); attackTime < mob.getSpecialData().getRangedAttackMaxCooldown();
} }
@Override /** Called when this AI is deactivated. */
public void start() {
runicEnderman.setBeamTargetId(runicEnderman.getTarget().getId());
attackTime = maxAttackTime;
}
@Override @Override
public void stop() { public void stop() {
runicEnderman.clearBeamTargetId(); targetPos = null;
attackTime = 0; mob.setBeamState( RunicEndermanEntity.BeamState.OFF );
} }
@SuppressWarnings("ConstantConditions") /** Called each tick while this AI is active. */
@Override @Override
public void tick() { public void tick() {
--attackTime; attackTime++;
switch( mob.getBeamState() ) {
if (attackTime <= 0) { case CHARGING:
LivingEntity target = runicEnderman.getTarget(); chargingTick();
double xRatio = MathHelper.sin(runicEnderman.yRot * ((float)Math.PI / 180F)); break;
double zRatio = -MathHelper.cos(runicEnderman.yRot * ((float)Math.PI / 180F)); case DAMAGING:
target.knockback(5, xRatio, zRatio); damagingTick();
target.hurt(DamageSource.mobAttack(runicEnderman), (float) baseDamage); break;
} }
} }
}
/** Called each tick while this AI is active and in the charging phase. */
private void chargingTick() {
mob.getLookControl().setLookAt( targetPos.x, targetPos.y, targetPos.z,
30.0F, mob.getMaxHeadXRot() );
if( attackTime >= mob.getSpecialData().getRangedAttackCooldown() ) {
mob.setBeamState( RunicEndermanEntity.BeamState.DAMAGING );
}
}
/** Called each tick while this AI is active and in the damaging phase. */
private void damagingTick() {
final LivingEntity target = mob.getTarget();
if( target != null ) setTargetPos( target );
lookTowardTarget( 1.666F * mob.getSpecialData().getRangedWalkSpeed() ); // Use move speed as a turn rate modifier
final Vector3d viewVec = mob.getViewVector( 1.0F ).scale( RunicEndermanEntity.BEAM_MAX_RANGE );
final Vector3d beamStartPos = mob.getEyePosition( 1.0F );
Vector3d beamEndPos = beamStartPos.add( viewVec );
final RayTraceResult blockRayTrace = mob.level.clip(
new RayTraceContext( beamStartPos, beamEndPos,
RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.NONE, mob ) );
if( blockRayTrace.getType() != RayTraceResult.Type.MISS ) {
beamEndPos = blockRayTrace.getLocation();
}
final List<Entity> hitEntities = rayTraceEntities( beamStartPos, beamEndPos,
mob.getBoundingBox().expandTowards( viewVec ).inflate( 1.0 ) );
for( Entity entity : hitEntities ) {
if( entity.hurt( beamDamageSource, mob.getSpecialData().getRangedAttackDamage() ) &&
entity instanceof LivingEntity ) {
MobHelper.knockback( (LivingEntity) entity, 1.0F, 1.0F,
-viewVec.x, -viewVec.z, 1.0 );
}
}
}
/** Updates the target position based on an entity we want to hit. */
private void setTargetPos( LivingEntity target ) {
targetPos = new Vector3d( target.getX(), target.getY( 0.5 ), target.getZ() );
}
/** Sets the target look position. Modifies the target position to limit vertical look speed. */
private void lookTowardTarget( float speed ) {
final double dX = targetPos.x - mob.getX();
final double dY = targetPos.y - mob.getEyeY();
final double dZ = targetPos.z - mob.getZ();
final double dH = MathHelper.sqrt( dX * dX + dZ * dZ );
final float targetXRot = (float) MathHelper.atan2( dY, dH );
final float clampedXRot = (mob.xRot + MathHelper.clamp( MathHelper.degreesDifference( mob.xRot,
targetXRot * -57.2957763671875F ), -speed, speed )) / -57.2957763671875F;
final double clampedDY = dH * MathHelper.sin( clampedXRot ) / MathHelper.cos( clampedXRot );
targetPos = targetPos.add( 0.0, clampedDY - dY, 0.0 );
mob.getLookControl().setLookAt( targetPos.x, targetPos.y, targetPos.z,
speed, mob.getMaxHeadXRot() );
}
/** @return A list of all entities between the two vector positions and within the search area, in no particular order. */
private List<Entity> rayTraceEntities( Vector3d from, Vector3d to, AxisAlignedBB searchArea ) {
final List<Entity> entitiesHit = new ArrayList<>();
for( Entity entity : mob.level.getEntities( mob, searchArea, this::canBeamHitTarget ) ) {
final Optional<Vector3d> hitPos = entity.getBoundingBox().inflate( 0.3 ).clip( from, to );
if( hitPos.isPresent() ) {
entitiesHit.add( entity );
}
}
return entitiesHit;
}
/** @return True if the beam can hit the target. */
private boolean canBeamHitTarget( @Nullable Entity entity ) {
return entity != null && !entity.isSpectator() && entity.isAlive() && entity.isPickable() &&
!entity.isPassengerOfSameVehicle( mob );
}
}

View file

@ -42,7 +42,7 @@ public class FishingDrownedEntity extends _SpecialDrownedEntity implements IAngl
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW ) bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW )
.addExperience( 2 ).drownImmune().fluidPushImmune() .addExperience( 2 ).drownImmune().fluidPushImmune()
.convertThrowToFishing().fishingAttack( 1.0, 40, 15.0 ) .convertRangedAttackToFishing( 1.0, 40, 15.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 ); .multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
} }

View file

@ -4,6 +4,7 @@ import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.goal.RunicEndermanBeamAttackGoal; import fathertoast.specialmobs.common.entity.ai.goal.RunicEndermanBeamAttackGoal;
import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
@ -16,9 +17,10 @@ import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.potion.Effects; import net.minecraft.potion.Effects;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import java.util.OptionalInt; import net.minecraftforge.api.distmarker.OnlyIn;
@SpecialMob @SpecialMob
public class RunicEndermanEntity extends _SpecialEndermanEntity { public class RunicEndermanEntity extends _SpecialEndermanEntity {
@ -33,10 +35,11 @@ public class RunicEndermanEntity extends _SpecialEndermanEntity {
bestiaryInfo.color( 0xE42281 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.MOUNTAIN ) bestiaryInfo.color( 0xE42281 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.MOUNTAIN )
.uniqueTextureWithEyes() .uniqueTextureWithEyes()
.addExperience( 2 ).fallImmune().burnImmune() .addExperience( 2 ).fallImmune().burnImmune()
.convertRangedAttackToBeam( 2.0, 1.0, 60, 100, 16.0 )
.addToAttribute( Attributes.ARMOR, 10.0 ) .addToAttribute( Attributes.ARMOR, 10.0 )
.addToAttribute( Attributes.ATTACK_DAMAGE, 1.0 ) .addToAttribute( Attributes.ATTACK_DAMAGE, 1.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 ); .multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
} }
@SpecialMob.LanguageProvider @SpecialMob.LanguageProvider
@ -62,42 +65,53 @@ public class RunicEndermanEntity extends _SpecialEndermanEntity {
//--------------- Variant-Specific Implementations ---------------- //--------------- Variant-Specific Implementations ----------------
public static final DataParameter<Boolean> BEAMING = EntityDataManager.defineId(RunicEndermanEntity.class, DataSerializers.BOOLEAN); /** The actual range of the beam when fired. Roughly based on end crystal max range. */
public static final DataParameter<OptionalInt> TARGET_ID = EntityDataManager.defineId(RunicEndermanEntity.class, DataSerializers.OPTIONAL_UNSIGNED_INT); public static final double BEAM_MAX_RANGE = 32.0;
/** The parameter for beam attack state. */
private static final DataParameter<Byte> BEAM_STATE = EntityDataManager.defineId( RunicEndermanEntity.class, DataSerializers.BYTE );
public RunicEndermanEntity( EntityType<? extends _SpecialEndermanEntity> entityType, World world ) { super( entityType, world ); } public RunicEndermanEntity( EntityType<? extends _SpecialEndermanEntity> entityType, World world ) { super( entityType, world ); }
/** Called from the Entity.class constructor to define data watcher variables. */
@Override @Override
protected void defineSynchedData() { protected void defineSynchedData() {
super.defineSynchedData(); super.defineSynchedData();
entityData.define( BEAMING, false ); entityData.define( BEAM_STATE, BeamState.OFF.id() );
entityData.define( TARGET_ID, OptionalInt.empty() );
} }
public OptionalInt getBeamTargetId() { /** Override to change this entity's AI goals. */
return entityData.get( TARGET_ID ); @Override
protected void registerVariantGoals() {
AIHelper.insertGoal( goalSelector, 2, new RunicEndermanBeamAttackGoal( this ) );
} }
public void setBeamTargetId(int targetId) {
entityData.set( TARGET_ID, OptionalInt.of(targetId) );
}
public void clearBeamTargetId() {
entityData.set( TARGET_ID, OptionalInt.empty() );
}
/** Override to apply effects when this entity hits a target with a melee attack. */ /** Override to apply effects when this entity hits a target with a melee attack. */
@Override @Override
protected void onVariantAttack( LivingEntity target ) { protected void onVariantAttack( LivingEntity target ) {
MobHelper.applyEffect( target, Effects.LEVITATION ); MobHelper.applyEffect( target, Effects.LEVITATION );
MobHelper.knockback( this, target, 2.0F, 0.0F ); MobHelper.knockback( this, target, 2.0F, 0.0F );
} }
/** Override to change this entity's AI goals. */ /** @return True if this enderman is using its beam attack. */
public BeamState getBeamState() { return BeamState.of( entityData.get( BEAM_STATE ) ); }
/** Sets whether this enderman is using its beam attack. */
public void setBeamState( BeamState value ) { entityData.set( BEAM_STATE, value.id() ); }
/** @return The bounding box to use for frustum culling. */
@OnlyIn( Dist.CLIENT )
@Override @Override
protected void registerVariantGoals() { public AxisAlignedBB getBoundingBoxForCulling() {
super.registerVariantGoals(); return getBeamState() == BeamState.OFF ? super.getBoundingBoxForCulling() :
goalSelector.addGoal( 2, new RunicEndermanBeamAttackGoal(this, 50, 2.0D)); super.getBoundingBoxForCulling().expandTowards( getViewVector( 1.0F ).scale( BEAM_MAX_RANGE ) );
}
public enum BeamState {
OFF, CHARGING, DAMAGING;
public byte id() { return (byte) ordinal(); }
public static BeamState of( byte id ) { return values()[id]; }
} }
} }

View file

@ -35,7 +35,7 @@ public class FishingSilverfishEntity extends AmphibiousSilverfishEntity implemen
.uniqueTextureBaseOnly() .uniqueTextureBaseOnly()
.size( 1.2F, 0.5F, 0.4F ) .size( 1.2F, 0.5F, 0.4F )
.addExperience( 2 ).drownImmune().fluidPushImmune() .addExperience( 2 ).drownImmune().fluidPushImmune()
.convertSpitToFishing().fishingAttack( 1.0, 40, 10.0 ) .convertRangedAttackToFishing( 1.0, 40, 10.0 )
.addToAttribute( Attributes.MAX_HEALTH, 4.0 ) .addToAttribute( Attributes.MAX_HEALTH, 4.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 ); .multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
} }

View file

@ -46,7 +46,7 @@ public class FishingZombieEntity extends _SpecialZombieEntity implements IAngler
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.FISHING ) bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.FISHING )
.addExperience( 2 ).drownImmune().fluidPushImmune() .addExperience( 2 ).drownImmune().fluidPushImmune()
.convertBowToFishing().fishingAttack( 1.0, 40, 15.0 ) .convertRangedAttackToFishing( 1.0, 40, 15.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 ); .multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
} }

View file

@ -44,7 +44,7 @@ public class FishingZombifiedPiglinEntity extends _SpecialZombifiedPiglinEntity
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.FISHING ) bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.FISHING )
.addExperience( 2 ).drownImmune().fluidPushImmune() .addExperience( 2 ).drownImmune().fluidPushImmune()
.convertBowToFishing().fishingAttack( 1.0, 40, 15.0 ) .convertRangedAttackToFishing( 1.0, 40, 15.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 ); .multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
} }