Implement sombra boss battle
1
assets/models/cloud_puff.bbmodel
Normal file
1
assets/models/clouds.bbmodel
Normal file
BIN
assets/models/clouds.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
|
@ -53,6 +53,10 @@ public interface USounds {
|
|||
SoundEvent ENTITY_HOT_AIR_BALLOON_EQUIP_CANOPY = ITEM_ARMOR_EQUIP_LEATHER;
|
||||
SoundEvent ENTITY_HOT_AIR_BALLOON_EQUIP_BURNER = ENTITY_IRON_GOLEM_DAMAGE;
|
||||
|
||||
SoundEvent ENTITY_SOMBRA_AMBIENT = register("entity.sombra.ambient");
|
||||
SoundEvent ENTITY_SOMBRA_LAUGH = register("entity.sombra.laugh");
|
||||
SoundEvent ENTITY_SOMBRA_SNICKER = register("entity.sombra.snicker");
|
||||
|
||||
SoundEvent ITEM_AMULET_CHARGING = register("item.amulet.charging");
|
||||
SoundEvent ITEM_AMULET_RECHARGE = register("item.amulet.recharge");
|
||||
|
||||
|
|
|
@ -79,6 +79,8 @@ public interface URenderers {
|
|||
EntityRendererRegistry.register(UEntities.TWITTERMITE, FairyEntityRenderer::new);
|
||||
EntityRendererRegistry.register(UEntities.SPELLBOOK, SpellbookEntityRenderer::new);
|
||||
EntityRendererRegistry.register(UEntities.SOMBRA, SombraEntityRenderer::new);
|
||||
EntityRendererRegistry.register(UEntities.CRYSTAL_SHARDS, CrystalShardsEntityRenderer::new);
|
||||
EntityRendererRegistry.register(UEntities.STORM_CLOUD, StormCloudEntityRenderer::new);
|
||||
EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new);
|
||||
|
||||
BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.minelittlepony.unicopia.client;
|
|||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.minelittlepony.common.client.gui.element.Button;
|
||||
import com.minelittlepony.common.event.ScreenInitCallback;
|
||||
import com.minelittlepony.common.event.ScreenInitCallback.ButtonList;
|
||||
|
@ -17,6 +19,7 @@ import com.minelittlepony.unicopia.container.*;
|
|||
import com.minelittlepony.unicopia.entity.player.PlayerCamera;
|
||||
import com.minelittlepony.unicopia.entity.player.Pony;
|
||||
import com.minelittlepony.unicopia.network.handler.ClientNetworkHandlerImpl;
|
||||
import com.minelittlepony.unicopia.server.world.WeatherConditions;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
|
@ -26,9 +29,11 @@ import net.minecraft.client.MinecraftClient;
|
|||
import net.minecraft.client.gui.screen.OpenToLanScreen;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreens;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.resource.ResourceType;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
public class UnicopiaClient implements ClientModInitializer {
|
||||
|
||||
|
@ -59,6 +64,9 @@ public class UnicopiaClient implements ClientModInitializer {
|
|||
return 0.6F;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Float originalRainGradient;
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
InteractionManager.INSTANCE = new ClientInteractionManager();
|
||||
|
@ -70,6 +78,7 @@ public class UnicopiaClient implements ClientModInitializer {
|
|||
HandledScreens.register(UScreenHandlers.SPELL_BOOK, SpellbookScreen::new);
|
||||
|
||||
ClientTickEvents.END_CLIENT_TICK.register(this::onTick);
|
||||
ClientTickEvents.END_WORLD_TICK.register(this::onWorldTick);
|
||||
ScreenInitCallback.EVENT.register(this::onScreenInit);
|
||||
ItemTooltipCallback.EVENT.register(new ModifierTooltipRenderer());
|
||||
|
||||
|
@ -83,6 +92,25 @@ public class UnicopiaClient implements ClientModInitializer {
|
|||
UHud.INSTANCE.tick();
|
||||
}
|
||||
|
||||
private void onWorldTick(ClientWorld world) {
|
||||
BlockPos pos = MinecraftClient.getInstance().getCameraEntity().getBlockPos();
|
||||
float tickDelta = MinecraftClient.getInstance().getTickDelta();
|
||||
|
||||
if (WeatherConditions.get(world).isInRangeOfStorm(pos)) {
|
||||
if (originalRainGradient == null) {
|
||||
originalRainGradient = world.getRainGradient(tickDelta);
|
||||
}
|
||||
world.setRainGradient(1);
|
||||
world.setThunderGradient(1);
|
||||
} else {
|
||||
if (originalRainGradient != null) {
|
||||
world.setRainGradient(originalRainGradient);
|
||||
world.setThunderGradient(0);
|
||||
originalRainGradient = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onScreenInit(Screen screen, ButtonList buttons) {
|
||||
if (screen instanceof OpenToLanScreen) {
|
||||
buttons.addButton(new Button(screen.width / 2 - 155, 130, 150, 20))
|
||||
|
|
|
@ -6,8 +6,8 @@ import java.util.Optional;
|
|||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import com.minelittlepony.unicopia.EntityConvertable;
|
||||
import com.minelittlepony.unicopia.Unicopia;
|
||||
import com.minelittlepony.unicopia.ability.magic.Caster;
|
||||
import com.minelittlepony.unicopia.entity.player.Pony;
|
||||
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
|
||||
import com.minelittlepony.unicopia.particle.ParticleHandle.Link;
|
||||
|
@ -79,8 +79,8 @@ public class RainbowTrailParticle extends AbstractBillboardParticle implements A
|
|||
}
|
||||
}
|
||||
|
||||
private void follow(Caster<?> caster) {
|
||||
Vec3d next = caster.getOriginVector();
|
||||
private void follow(EntityConvertable<?> caster) {
|
||||
Vec3d next = caster.asEntity().getPos();
|
||||
|
||||
if (segments.isEmpty()) {
|
||||
segments.add(new Segment(next));
|
||||
|
|
|
@ -6,8 +6,8 @@ import org.joml.Quaternionf;
|
|||
import org.joml.Vector3f;
|
||||
|
||||
import com.minelittlepony.common.util.Color;
|
||||
import com.minelittlepony.unicopia.EntityConvertable;
|
||||
import com.minelittlepony.unicopia.Unicopia;
|
||||
import com.minelittlepony.unicopia.ability.magic.Caster;
|
||||
import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
|
||||
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
|
||||
import com.minelittlepony.unicopia.particle.ParticleHandle.Link;
|
||||
|
@ -16,6 +16,7 @@ import com.mojang.blaze3d.systems.RenderSystem;
|
|||
import net.minecraft.client.render.BufferBuilder;
|
||||
import net.minecraft.client.render.Tessellator;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.*;
|
||||
|
||||
|
@ -62,7 +63,7 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
|
|||
velocityX = 0;
|
||||
velocityY = 0;
|
||||
velocityZ = 0;
|
||||
Vec3d pos = link.get().map(Caster::getOriginVector).orElse(Vec3d.ZERO);
|
||||
Vec3d pos = link.get().map(EntityConvertable::asEntity).map(Entity::getPos).orElse(Vec3d.ZERO);
|
||||
setPos(pos.x, pos.y + 0.25, pos.z);
|
||||
}
|
||||
|
||||
|
@ -164,7 +165,7 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
|
|||
public void tick() {
|
||||
super.tick();
|
||||
|
||||
link.flatMap(Link::get).map(Caster::asEntity).ifPresentOrElse(e -> {
|
||||
link.flatMap(Link::get).map(EntityConvertable::asEntity).ifPresentOrElse(e -> {
|
||||
if (getAlphaScale() >= 0.9F) {
|
||||
if (stasisAge < 0) {
|
||||
stasisAge = age;
|
||||
|
|
|
@ -12,7 +12,7 @@ import net.minecraft.client.world.ClientWorld;
|
|||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
import com.minelittlepony.unicopia.ability.magic.Caster;
|
||||
import com.minelittlepony.unicopia.EntityConvertable;
|
||||
import com.minelittlepony.unicopia.client.render.RenderLayers;
|
||||
import com.minelittlepony.unicopia.client.render.model.SphereModel;
|
||||
import com.minelittlepony.unicopia.particle.SphereParticleEffect;
|
||||
|
@ -38,6 +38,8 @@ public class SphereParticle extends Particle implements Attachment {
|
|||
|
||||
private final SphereParticleEffect parameters;
|
||||
|
||||
private boolean bound;
|
||||
|
||||
public SphereParticle(SphereParticleEffect parameters, ClientWorld w, double x, double y, double z, double vX, double vY, double vZ) {
|
||||
this(parameters, w, x, y, z);
|
||||
|
||||
|
@ -90,6 +92,9 @@ public class SphereParticle extends Particle implements Attachment {
|
|||
if (key == ATTR_OPACITY) {
|
||||
alpha = value.floatValue();
|
||||
}
|
||||
if (key == ATTR_BOUND) {
|
||||
bound = value.intValue() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -102,13 +107,15 @@ public class SphereParticle extends Particle implements Attachment {
|
|||
super.tick();
|
||||
|
||||
if (link.isPresent()) {
|
||||
link.flatMap(Link::get).map(Caster::asEntity).ifPresentOrElse(e -> {
|
||||
Vec3d offset = parameters.getOffset();
|
||||
setPos(e.getX() + offset.getX(), e.getY() + offset.getY(), e.getZ() + offset.getZ());
|
||||
link.flatMap(Link::get).map(EntityConvertable::asEntity).ifPresentOrElse(e -> {
|
||||
if (!bound) {
|
||||
Vec3d offset = parameters.getOffset();
|
||||
setPos(e.getX() + offset.getX(), e.getY() + offset.getY(), e.getZ() + offset.getZ());
|
||||
|
||||
prevPosX = e.lastRenderX + offset.getX();
|
||||
prevPosY = e.lastRenderY + offset.getY();
|
||||
prevPosZ = e.lastRenderZ + offset.getZ();
|
||||
prevPosX = e.lastRenderX + offset.getX();
|
||||
prevPosY = e.lastRenderY + offset.getY();
|
||||
prevPosZ = e.lastRenderZ + offset.getZ();
|
||||
}
|
||||
}, this::detach);
|
||||
|
||||
if (steps-- > 0) {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package com.minelittlepony.unicopia.client.render.entity;
|
||||
|
||||
import com.minelittlepony.unicopia.entity.CrystalShardsEntity;
|
||||
|
||||
import net.minecraft.client.model.Dilation;
|
||||
import net.minecraft.client.model.ModelData;
|
||||
import net.minecraft.client.model.ModelPart;
|
||||
import net.minecraft.client.model.ModelPartBuilder;
|
||||
import net.minecraft.client.model.ModelPartData;
|
||||
import net.minecraft.client.model.ModelTransform;
|
||||
import net.minecraft.client.model.TexturedModelData;
|
||||
import net.minecraft.client.render.VertexConsumer;
|
||||
import net.minecraft.client.render.entity.model.EntityModel;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
|
||||
public class CrystalShardsEntityModel extends EntityModel<CrystalShardsEntity> {
|
||||
private final ModelPart part;
|
||||
|
||||
public CrystalShardsEntityModel(ModelPart root) {
|
||||
this.part = root;
|
||||
}
|
||||
|
||||
public static TexturedModelData getTexturedModelData() {
|
||||
ModelData data = new ModelData();
|
||||
ModelPartData root = data.getRoot();
|
||||
root.addChild("west", ModelPartBuilder.create().uv(12, 0).cuboid(-1, -10, -2, 3, 10, 3, Dilation.NONE), ModelTransform.rotation(0.4075F, 0.0378F, 1.0216F));
|
||||
root.addChild("north", ModelPartBuilder.create().uv(12, 0).cuboid(-1, -10, -2, 3, 10, 3, Dilation.NONE), ModelTransform.rotation(1.817F, 0.6399F, 2.0582F));
|
||||
root.addChild("south", ModelPartBuilder.create().uv(12, 0).cuboid(-2, -9, -2, 3, 10, 3, Dilation.NONE), ModelTransform.rotation(-1.8254F, 0.6819F, -1.3949F));
|
||||
root.addChild("east", ModelPartBuilder.create().uv(12, 13).cuboid(-2, -8, -2, 3, 8, 3, Dilation.NONE), ModelTransform.rotation(-0.1151F, 0.3935F, -0.7545F));
|
||||
root.addChild("primary", ModelPartBuilder.create().uv(0, 0).cuboid(-2, -20, -2, 3, 20, 3, Dilation.NONE), ModelTransform.rotation(0.2411F, 0.3339F, 0.1819F));
|
||||
return TexturedModelData.of(data, 32, 32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAngles(CrystalShardsEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha) {
|
||||
part.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.minelittlepony.unicopia.client.render.entity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.minelittlepony.unicopia.Unicopia;
|
||||
import com.minelittlepony.unicopia.entity.CrystalShardsEntity;
|
||||
import net.minecraft.client.render.OverlayTexture;
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.render.entity.EntityRenderer;
|
||||
import net.minecraft.client.render.entity.EntityRendererFactory;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.RotationAxis;
|
||||
|
||||
public class CrystalShardsEntityRenderer extends EntityRenderer<CrystalShardsEntity> {
|
||||
private static final Identifier TEXTURE = Unicopia.id("textures/entity/crystal_shards/normal.png");
|
||||
private static final Identifier[] CORRUPTED = List.of("corrupt", "dark", "darker").stream()
|
||||
.map(name -> Unicopia.id("textures/entity/crystal_shards/" + name + ".png"))
|
||||
.toArray(Identifier[]::new);
|
||||
|
||||
private final CrystalShardsEntityModel model;
|
||||
|
||||
public CrystalShardsEntityRenderer(EntityRendererFactory.Context context) {
|
||||
super(context);
|
||||
model = new CrystalShardsEntityModel(CrystalShardsEntityModel.getTexturedModelData().createModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(CrystalShardsEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
|
||||
matrices.push();
|
||||
matrices.multiply(entity.getAttachmentFace().getRotationQuaternion());
|
||||
matrices.scale(-1, -1, 1);
|
||||
|
||||
float scale = entity.getGrowth(tickDelta);
|
||||
matrices.scale(scale, scale, scale);
|
||||
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
|
||||
|
||||
model.setAngles(entity, 0, 0, 0, 0, 0);
|
||||
model.render(matrices, vertices.getBuffer(model.getLayer(getTexture(entity))), light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1);
|
||||
matrices.pop();
|
||||
super.render(entity, yaw, tickDelta, matrices, vertices, light);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getTexture(CrystalShardsEntity entity) {
|
||||
if (entity.isCorrupt()) {
|
||||
return CORRUPTED[(int)(Math.abs(entity.getUuid().getMostSignificantBits()) % CORRUPTED.length)];
|
||||
}
|
||||
return TEXTURE;
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ public class SombraEntityModel extends EntityModel<SombraEntity> {
|
|||
private final ModelPart part;
|
||||
|
||||
private final ModelPart head;
|
||||
private final ModelPart mane;
|
||||
private final ModelPart upperJaw;
|
||||
private final ModelPart lowerJaw;
|
||||
|
||||
|
@ -28,7 +27,6 @@ public class SombraEntityModel extends EntityModel<SombraEntity> {
|
|||
public SombraEntityModel(ModelPart root) {
|
||||
this.part = root;
|
||||
this.head = root.getChild("head");
|
||||
this.mane = head.getChild("mane");
|
||||
this.upperJaw = head.getChild("upper_jaw");
|
||||
this.lowerJaw = head.getChild("lower_jaw");
|
||||
this.body = root.getChild("body");
|
||||
|
@ -84,27 +82,34 @@ public class SombraEntityModel extends EntityModel<SombraEntity> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAngles(SombraEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
|
||||
public void animateModel(SombraEntity entity, float limbAngle, float limbDistance, float tickDelta) {
|
||||
float jawsOpenAmount = entity.getBiteAmount(tickDelta); //(1 - Math.max(0, MathHelper.sin(ageInTicks * 0.1F)));
|
||||
|
||||
lowerJaw.resetTransform();
|
||||
lowerJaw.pivotY -= jawsOpenAmount * 3;
|
||||
lowerJaw.pivotX -= jawsOpenAmount * 3;
|
||||
lowerJaw.roll += jawsOpenAmount - 0.9F;
|
||||
|
||||
upperJaw.resetTransform();
|
||||
upperJaw.roll -= jawsOpenAmount * 0.2F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAngles(SombraEntity entity, float limbAngle, float limbDistance, float animationProgress, float netHeadYaw, float headPitch) {
|
||||
float scale = 1.6F;
|
||||
|
||||
part.xScale = scale;
|
||||
part.yScale = scale;
|
||||
part.zScale = scale;
|
||||
|
||||
part.yaw = -MathHelper.HALF_PI;
|
||||
part.pivotY = MathHelper.sin(ageInTicks * 0.05F) - 3;
|
||||
part.pivotZ = MathHelper.cos(ageInTicks * 0.045F);
|
||||
|
||||
//part.yaw = (float)entity.getVelocity().getX();
|
||||
part.pivotY = MathHelper.sin(animationProgress * 0.05F) - 12;
|
||||
part.pivotZ = MathHelper.cos(animationProgress * 0.045F);
|
||||
|
||||
head.pitch = headPitch * MathHelper.RADIANS_PER_DEGREE;
|
||||
head.yaw = netHeadYaw * MathHelper.RADIANS_PER_DEGREE;
|
||||
|
||||
lowerJaw.resetTransform();
|
||||
float jawsOpenAmount = (1 - Math.max(0, MathHelper.sin(ageInTicks * 0.1F)));
|
||||
lowerJaw.pivotY -= jawsOpenAmount * 3;
|
||||
lowerJaw.pivotX -= jawsOpenAmount * 3;
|
||||
lowerJaw.roll += jawsOpenAmount - 0.9F;
|
||||
|
||||
upperJaw.resetTransform();
|
||||
upperJaw.roll -= jawsOpenAmount * 0.2F;
|
||||
|
||||
body.roll = limbSwingAmount * 0.3F;
|
||||
body.roll = limbDistance * 0.3F;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
package com.minelittlepony.unicopia.client.render.entity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import com.minelittlepony.unicopia.entity.StormCloudEntity;
|
||||
|
||||
import net.minecraft.client.model.Dilation;
|
||||
import net.minecraft.client.model.ModelData;
|
||||
import net.minecraft.client.model.ModelPart;
|
||||
import net.minecraft.client.model.ModelPartBuilder;
|
||||
import net.minecraft.client.model.ModelPartData;
|
||||
import net.minecraft.client.model.ModelTransform;
|
||||
import net.minecraft.client.model.TexturedModelData;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.render.VertexConsumer;
|
||||
import net.minecraft.client.render.entity.model.EntityModel;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.random.Random;
|
||||
|
||||
public class StormCloudEntityModel extends EntityModel<StormCloudEntity> {
|
||||
private final ModelPart part;
|
||||
|
||||
private final ModelPart smallPuffs;
|
||||
private final List<ModelPart> smallPuffCubes;
|
||||
|
||||
private final ModelPart anvilHeads;
|
||||
private final List<ModelPart> anvilHeadCubes;
|
||||
|
||||
private final ModelPart puff;
|
||||
|
||||
private final Random rng = Random.create(0);
|
||||
private final Vector3f puffLocation = new Vector3f();
|
||||
|
||||
public StormCloudEntityModel(ModelPart root) {
|
||||
super(RenderLayer::getEntityTranslucent);
|
||||
this.part = root;
|
||||
this.smallPuffs = part.getChild("small_puffs");
|
||||
this.smallPuffCubes = smallPuffs.traverse().toList();
|
||||
this.anvilHeads = part.getChild("anvil_heads");
|
||||
this.anvilHeadCubes = anvilHeads.traverse().toList();
|
||||
this.puff = part.getChild("puff");
|
||||
}
|
||||
|
||||
public static TexturedModelData getTexturedModelData() {
|
||||
ModelData data = new ModelData();
|
||||
ModelPartData root = data.getRoot();
|
||||
ModelPartData small_puffs = root.addChild("small_puffs", ModelPartBuilder.create(), ModelTransform.pivot(0, 24, 0));
|
||||
|
||||
small_puffs.addChild("cube_r1", ModelPartBuilder.create()
|
||||
.uv(0, 37).cuboid(-2, -20, -20, 7, 17, 20, Dilation.NONE)
|
||||
.uv(0, 37).cuboid(-2, -13, -40, 7, 17, 20, Dilation.NONE), ModelTransform.of(0, -6, 0, 0, 0, 1.5708F));
|
||||
|
||||
small_puffs.addChild("cube_r2", ModelPartBuilder.create().uv(0, 37).cuboid(-2, -7, -25, 7, 17, 20, Dilation.NONE), ModelTransform.of(0, -6, 0, 0.1745F, 0, 1.5708F));
|
||||
small_puffs.addChild("cube_r3", ModelPartBuilder.create().uv(0, 37).cuboid(-7, -7, -7, 7, 17, 20, Dilation.NONE), ModelTransform.of(-15, -1, 15, 0.2182F, 0, 1.5708F));
|
||||
small_puffs.addChild("cube_r4", ModelPartBuilder.create().cuboid(-13, -14, -31, 7, 17, 20, Dilation.NONE), ModelTransform.of(2, -2, 19, 0, 0, 1.6581F));
|
||||
small_puffs.addChild("cube_r5", ModelPartBuilder.create().uv(0, 37).cuboid(-6, -17, -6, 7, 17, 20, Dilation.NONE), ModelTransform.of(2, -2, 19, -0.3491F, 0, 1.5708F));
|
||||
small_puffs.addChild("cube_r6", ModelPartBuilder.create().cuboid(-3.5F, -4.5F, -23, 7, 17, 20, Dilation.NONE), ModelTransform.of(0.4471F, -12.1925F, 36, 0, 0, -3.1416F));
|
||||
small_puffs.addChild("cube_r7", ModelPartBuilder.create().uv(0, 37).cuboid(-6, -17, -12, 7, 17, 20, Dilation.NONE), ModelTransform.of(-15, -2, 26, -0.829F, 0, 1.5708F));
|
||||
small_puffs.addChild("cube_r8", ModelPartBuilder.create()
|
||||
.uv(0, 37).cuboid(-6, -20, -21, 7, 17, 20, Dilation.NONE)
|
||||
.cuboid(-13, -20, -40, 7, 17, 20, Dilation.NONE), ModelTransform.of(-15, -2, 26, 0, 0, 1.5708F));
|
||||
|
||||
small_puffs.addChild("cube_r9", ModelPartBuilder.create().cuboid(-15, -13, -18, 7, 17, 20, Dilation.NONE), ModelTransform.of(-15, -1, 15, 0, 0, 1.4835F));
|
||||
small_puffs.addChild("cube_r10", ModelPartBuilder.create().uv(0, 37).cuboid(-7, -3, -25, 7, 17, 20, Dilation.NONE), ModelTransform.of(-15, -1, 15, 0, 0, 1.5708F));
|
||||
small_puffs.addChild("cube_r11", ModelPartBuilder.create().uv(0, 37).cuboid(-2, -4, -7, 7, 17, 20, Dilation.NONE), ModelTransform.of(0, -6, 0, -0.0873F, 0, 1.5708F));
|
||||
small_puffs.addChild("cube_r12", ModelPartBuilder.create().cuboid(-9.5F, -8.5F, -10, 7, 17, 20, Dilation.NONE), ModelTransform.of(8.842F, -13.6476F, -20.7083F, 0.1739F, 0.0151F, -0.0423F));
|
||||
small_puffs.addChild("cube_r13", ModelPartBuilder.create().uv(0, 37).cuboid(-2, -19, -6, 7, 17, 20, Dilation.NONE), ModelTransform.of(0, -6, 0, -0.3043F, 0, 1.5708F));
|
||||
|
||||
ModelPartData anvil_heads = root.addChild("anvil_heads", ModelPartBuilder.create(), ModelTransform.pivot(-2, 1, 0));
|
||||
anvil_heads.addChild("cube_r14", ModelPartBuilder.create().cuboid(-3.5F, -1.5F, -4, 7, 17, 20, new Dilation(2)), ModelTransform.of(-5.5F, -4.5F, 16, -0.3927F, 0, 0));
|
||||
anvil_heads.addChild("cube_r15", ModelPartBuilder.create().cuboid(-2, -18, 4, 7, 17, 20, new Dilation(5)), ModelTransform.of(0, -6, 0, -0.1309F, 0, 0));
|
||||
anvil_heads.addChild("cube_r16", ModelPartBuilder.create().cuboid(0, -1.5F, -12.5F, 7, 17, 20, new Dilation(5)), ModelTransform.of(0, -6, 0, 0.3491F, 0, 0));
|
||||
anvil_heads.addChild("cube_r17", ModelPartBuilder.create().cuboid(7, 9, 0, 7, 17, 20, new Dilation(2)), ModelTransform.of(0, -6, 0, 0.2618F, 0, 0));
|
||||
anvil_heads.addChild("cube_r18", ModelPartBuilder.create().cuboid(-12, 3, -9, 7, 17, 20, new Dilation(2)), ModelTransform.of(0, -6, 0, 0.3927F, 0, 0));
|
||||
|
||||
root.addChild("puff", ModelPartBuilder.create()
|
||||
.uv(0, 77).cuboid(-6, -11, -5, 18, 5, 9, Dilation.NONE)
|
||||
.uv(0, 80).cuboid(-8, -10, 4, 14, 4, 6, Dilation.NONE)
|
||||
.uv(0, 74).cuboid(-2, -10, -11, 12, 5, 11, Dilation.NONE)
|
||||
.uv(7, 77).cuboid(-14, -11, -10, 12, 5, 9, Dilation.NONE)
|
||||
.uv(0, 79).cuboid(-2, -13, 0, 8, 3, 6, Dilation.NONE), ModelTransform.of(-5, 14, -20, 0, 1.5272F, 0));
|
||||
|
||||
return TexturedModelData.of(data, 64, 128);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAngles(StormCloudEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
|
||||
float dir = 1;
|
||||
float globalScale = 0.012F;
|
||||
|
||||
float roll = MathHelper.cos(entity.age / 10F) * globalScale;
|
||||
float flop = MathHelper.sin(entity.age / 30F) * 0.02F * globalScale;
|
||||
|
||||
for (ModelPart swirl : smallPuffCubes) {
|
||||
swirl.resetTransform();
|
||||
dir = -dir;
|
||||
swirl.yaw += roll * 0.01F * dir;
|
||||
}
|
||||
|
||||
for (ModelPart swirl : anvilHeadCubes) {
|
||||
swirl.resetTransform();
|
||||
dir = -dir;
|
||||
swirl.pitch += flop + roll * -0.01F * dir;
|
||||
swirl.yaw += roll * 0.01F * dir;
|
||||
swirl.roll += -flop + roll * 0.01F * dir;
|
||||
}
|
||||
|
||||
part.pivotY = MathHelper.sin(entity.age * 0.25F) * 0.03F * globalScale;
|
||||
part.pivotX = MathHelper.cos(entity.age * 0.05125F) * 0.7F * globalScale;
|
||||
part.pivotZ = MathHelper.sin(entity.age * 0.05125F) * 0.7F * globalScale;
|
||||
|
||||
rng.setSeed(entity.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha) {
|
||||
matrices.push();
|
||||
part.rotate(matrices);
|
||||
smallPuffs.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
|
||||
anvilHeads.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
|
||||
|
||||
|
||||
int puffCount = rng.nextInt(7);
|
||||
for (int i = 0; i < puffCount; i++) {
|
||||
puff.resetTransform();
|
||||
puff.translate(puffLocation.set(
|
||||
rng.nextGaussian(),
|
||||
rng.nextGaussian(),
|
||||
rng.nextGaussian()
|
||||
).mul(16));
|
||||
puff.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
|
||||
}
|
||||
matrices.pop();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package com.minelittlepony.unicopia.client.render.entity;
|
||||
|
||||
import com.minelittlepony.unicopia.Unicopia;
|
||||
import com.minelittlepony.unicopia.entity.StormCloudEntity;
|
||||
|
||||
import net.minecraft.client.render.Frustum;
|
||||
import net.minecraft.client.render.OverlayTexture;
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.render.entity.EntityRenderer;
|
||||
import net.minecraft.client.render.entity.EntityRendererFactory;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.RotationAxis;
|
||||
|
||||
public class StormCloudEntityRenderer extends EntityRenderer<StormCloudEntity> {
|
||||
private static final Identifier TEXTURE = Unicopia.id("textures/entity/storm_cloud.png");
|
||||
|
||||
private final StormCloudEntityModel model;
|
||||
|
||||
public StormCloudEntityRenderer(EntityRendererFactory.Context context) {
|
||||
super(context);
|
||||
model = new StormCloudEntityModel(StormCloudEntityModel.getTexturedModelData().createModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(StormCloudEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
|
||||
matrices.push();
|
||||
matrices.scale(-1, -1, 1);
|
||||
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
|
||||
|
||||
float scale = entity.getSize(tickDelta);
|
||||
|
||||
matrices.scale(scale, scale, scale);
|
||||
matrices.translate(0, -1.45F, 0);
|
||||
|
||||
model.setAngles(entity, 0, 0, 0, 0, 0);
|
||||
model.render(matrices, vertices.getBuffer(model.getLayer(getTexture(entity))),
|
||||
entity.isStormy() ? 0 : light,
|
||||
OverlayTexture.DEFAULT_UV, 1, 1, 1, 0.9F);
|
||||
matrices.pop();
|
||||
super.render(entity, yaw, tickDelta, matrices, vertices, light);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getTexture(StormCloudEntity entity) {
|
||||
return TEXTURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRender(StormCloudEntity entity, Frustum frustum, double x, double y, double z) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.minelittlepony.unicopia.entity;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
public interface ArenaCombatant {
|
||||
Optional<BlockPos> getHomePos();
|
||||
|
||||
default float getAreaRadius() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
boolean teleportTo(Vec3d destination);
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
package com.minelittlepony.unicopia.entity;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.minelittlepony.unicopia.USounds;
|
||||
import com.minelittlepony.unicopia.item.UItems;
|
||||
|
||||
import net.minecraft.block.SideShapeType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.data.DataTracker;
|
||||
import net.minecraft.entity.data.TrackedData;
|
||||
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
|
||||
import net.minecraft.fluid.Fluids;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.predicate.entity.EntityPredicates;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Box;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class CrystalShardsEntity extends Entity {
|
||||
static final byte SHAKE = 1;
|
||||
|
||||
static final int FULL_GROWTH_AGE = 25;
|
||||
|
||||
private static final Set<Direction> ALL_DIRECTIONS = Set.of(Direction.values());
|
||||
private static final TrackedData<Direction> ATTACHMENT_FACE = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.FACING);
|
||||
private static final TrackedData<Integer> GROWTH = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
||||
private static final TrackedData<Boolean> DECAYING = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
|
||||
private static final TrackedData<Boolean> CORRUPT = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
|
||||
|
||||
public static boolean infestBlock(ServerWorld world, BlockPos pos) {
|
||||
if (world.isAir(pos) || !world.getFluidState(pos).isOf(Fluids.EMPTY)) {
|
||||
return false;
|
||||
}
|
||||
boolean success = false;
|
||||
for (Direction face : getFreeFaces(world, pos)) {
|
||||
CrystalShardsEntity shards = UEntities.CRYSTAL_SHARDS.create(world);
|
||||
shards.setPosition(pos.offset(face).toCenterPos());
|
||||
shards.setAttachmentFace(face);
|
||||
shards.setYaw(world.getRandom().nextFloat() * 360);
|
||||
shards.setCorrupt(true);
|
||||
|
||||
world.spawnEntity(shards);
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static Set<Direction> getFreeFaces(World world, BlockPos pos) {
|
||||
Set<Direction> freeFaces = new HashSet<>(ALL_DIRECTIONS);
|
||||
freeFaces.removeAll(getOccupiedFaces(world, pos));
|
||||
freeFaces.removeIf(face -> isInvalid(world, pos.offset(face), face));
|
||||
return freeFaces;
|
||||
}
|
||||
|
||||
public static Set<Direction> getOccupiedFaces(World world, BlockPos pos) {
|
||||
return world.getEntitiesByClass(CrystalShardsEntity.class, new Box(pos).expand(1), EntityPredicates.VALID_ENTITY)
|
||||
.stream()
|
||||
.map(e -> e.getAttachmentFace())
|
||||
.distinct()
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
static boolean isInvalid(World world, BlockPos crystalPos, Direction attachmentFace) {
|
||||
if (!world.isAir(crystalPos)) {
|
||||
return true;
|
||||
}
|
||||
BlockPos attachmentPos = crystalPos.offset(attachmentFace.getOpposite());
|
||||
return !world.getBlockState(attachmentPos).isSideSolid(world, attachmentPos, attachmentFace, SideShapeType.RIGID);
|
||||
}
|
||||
|
||||
private int prevAge;
|
||||
|
||||
private int ticksShaking;
|
||||
|
||||
public CrystalShardsEntity(EntityType<CrystalShardsEntity> type, World world) {
|
||||
super(type, world);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataTracker() {
|
||||
dataTracker.startTracking(ATTACHMENT_FACE, Direction.UP);
|
||||
dataTracker.startTracking(GROWTH, 0);
|
||||
dataTracker.startTracking(DECAYING, false);
|
||||
dataTracker.startTracking(CORRUPT, false);
|
||||
}
|
||||
|
||||
public float getGrowth(float tickDelta) {
|
||||
int age = getGrowth();
|
||||
float lerped = MathHelper.clamp(MathHelper.lerp(tickDelta, prevAge, age), 0, FULL_GROWTH_AGE) / (float)FULL_GROWTH_AGE;
|
||||
return lerped * 2 * (1 + Math.abs(getUuid().getLeastSignificantBits() % 20) / 20F);
|
||||
}
|
||||
|
||||
public int getGrowth() {
|
||||
return dataTracker.get(GROWTH);
|
||||
}
|
||||
|
||||
public void setGrowth(int growth) {
|
||||
dataTracker.set(GROWTH, Math.max(0, growth));
|
||||
}
|
||||
|
||||
public void setDecaying(boolean decaying) {
|
||||
dataTracker.set(DECAYING, decaying);
|
||||
}
|
||||
|
||||
public boolean isDecaying() {
|
||||
return dataTracker.get(DECAYING);
|
||||
}
|
||||
public void setCorrupt(boolean corrupted) {
|
||||
dataTracker.set(CORRUPT, corrupted);
|
||||
}
|
||||
|
||||
public boolean isCorrupt() {
|
||||
return dataTracker.get(CORRUPT);
|
||||
}
|
||||
|
||||
public boolean isShaking() {
|
||||
return ticksShaking > 0;
|
||||
}
|
||||
|
||||
public Direction getAttachmentFace() {
|
||||
return Objects.requireNonNullElse(dataTracker.get(ATTACHMENT_FACE), Direction.UP);
|
||||
}
|
||||
|
||||
public void setAttachmentFace(Direction face) {
|
||||
dataTracker.set(ATTACHMENT_FACE, face);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
prevAge = getGrowth();
|
||||
if (!getWorld().isClient) {
|
||||
int growAmount = 1 + Math.abs((int)getUuid().getLeastSignificantBits() % 5);
|
||||
setGrowth(prevAge + (isDecaying() ? -growAmount : growAmount));
|
||||
}
|
||||
setFireTicks(0);
|
||||
|
||||
if (ticksShaking > 0) {
|
||||
ticksShaking--;
|
||||
}
|
||||
|
||||
setPosition(getBlockPos().toCenterPos());
|
||||
|
||||
super.tick();
|
||||
|
||||
if (getGrowth() < FULL_GROWTH_AGE) {
|
||||
playSound(USounds.Vanilla.BLOCK_AMETHYST_BLOCK_CHIME, 1, 1);
|
||||
}
|
||||
|
||||
if (isInvalid(getWorld(), getBlockPos(), getAttachmentFace())) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean damage(DamageSource source, float amount) {
|
||||
getWorld().sendEntityStatus(this, SHAKE);
|
||||
return super.damage(source, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(RemovalReason reason) {
|
||||
if (reason == RemovalReason.KILLED) {
|
||||
playSound(USounds.Vanilla.BLOCK_AMETHYST_BLOCK_BREAK, 1, 1);
|
||||
dropStack(new ItemStack(UItems.CRYSTAL_SHARD, 6));
|
||||
}
|
||||
super.remove(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleStatus(byte status) {
|
||||
switch (status) {
|
||||
case SHAKE:
|
||||
ticksShaking = 30;
|
||||
break;
|
||||
default:
|
||||
super.handleStatus(status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeCustomDataToNbt(NbtCompound nbt) {
|
||||
nbt.putFloat("yaw", this.getYaw());
|
||||
nbt.putInt("growth", getGrowth());
|
||||
nbt.putString("face", getAttachmentFace().getName());
|
||||
nbt.putBoolean("decaying", isDecaying());
|
||||
nbt.putBoolean("corrupt", isCorrupt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readCustomDataFromNbt(NbtCompound nbt) {
|
||||
setYaw(nbt.getFloat("yaw"));
|
||||
setGrowth(nbt.getInt("growth"));
|
||||
setAttachmentFace(Direction.byName(nbt.getString("face")));
|
||||
setDecaying(nbt.getBoolean("decaying"));
|
||||
setCorrupt(nbt.getBoolean("corrupt"));
|
||||
prevAge = getGrowth();
|
||||
}
|
||||
}
|
|
@ -1,28 +1,41 @@
|
|||
package com.minelittlepony.unicopia.entity;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.minelittlepony.unicopia.USounds;
|
||||
import com.minelittlepony.unicopia.entity.ai.ArenaAttackGoal;
|
||||
import com.minelittlepony.unicopia.item.AmuletItem;
|
||||
import com.minelittlepony.unicopia.item.UItems;
|
||||
import com.minelittlepony.unicopia.particle.ParticleHandle;
|
||||
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
|
||||
import com.minelittlepony.unicopia.particle.ParticleSource;
|
||||
import com.minelittlepony.unicopia.particle.ParticleUtils;
|
||||
import com.minelittlepony.unicopia.particle.SphereParticleEffect;
|
||||
import com.minelittlepony.unicopia.particle.UParticles;
|
||||
import com.minelittlepony.unicopia.util.VecHelper;
|
||||
import com.minelittlepony.unicopia.util.shape.Sphere;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.entity.ai.goal.ActiveTargetGoal;
|
||||
import net.minecraft.entity.ai.goal.AttackGoal;
|
||||
import net.minecraft.entity.ai.goal.FlyGoal;
|
||||
import net.minecraft.entity.ai.goal.LongDoorInteractGoal;
|
||||
import net.minecraft.entity.ai.goal.LookAroundGoal;
|
||||
import net.minecraft.entity.ai.goal.LookAtEntityGoal;
|
||||
import net.minecraft.entity.ai.goal.PounceAtTargetGoal;
|
||||
import net.minecraft.entity.ai.goal.RevengeGoal;
|
||||
import net.minecraft.entity.ai.goal.WanderAroundGoal;
|
||||
import net.minecraft.entity.ai.pathing.BirdPathNodeMaker;
|
||||
import net.minecraft.entity.ai.pathing.EntityNavigation;
|
||||
import net.minecraft.entity.ai.pathing.MobNavigation;
|
||||
import net.minecraft.entity.ai.pathing.PathNodeNavigator;
|
||||
import net.minecraft.entity.ai.pathing.PathNodeType;
|
||||
import net.minecraft.entity.attribute.DefaultAttributeContainer;
|
||||
import net.minecraft.entity.attribute.EntityAttributes;
|
||||
import net.minecraft.entity.boss.BossBar;
|
||||
|
@ -41,20 +54,26 @@ import net.minecraft.item.ItemStack;
|
|||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.nbt.NbtElement;
|
||||
import net.minecraft.nbt.NbtHelper;
|
||||
import net.minecraft.particle.ParticleEffect;
|
||||
import net.minecraft.particle.ParticleTypes;
|
||||
import net.minecraft.predicate.entity.EntityPredicates;
|
||||
import net.minecraft.registry.tag.*;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.sound.SoundEvent;
|
||||
import net.minecraft.sound.SoundEvents;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.event.GameEvent;
|
||||
|
||||
public class SombraEntity extends HostileEntity {
|
||||
public class SombraEntity extends HostileEntity implements ArenaCombatant, ParticleSource<SombraEntity> {
|
||||
static final byte BITE = 70;
|
||||
static final int MAX_BITE_TIME = 20;
|
||||
static final Predicate<Entity> EFFECT_TARGET_PREDICATE = EntityPredicates.VALID_LIVING_ENTITY.and(EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR);
|
||||
|
||||
private static final TrackedData<Optional<BlockPos>> HOME_POS = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.OPTIONAL_BLOCK_POS);
|
||||
|
||||
|
@ -62,6 +81,21 @@ public class SombraEntity extends HostileEntity {
|
|||
.setDarkenSky(true)
|
||||
.setThickenFog(true);
|
||||
|
||||
private final ParticleHandle shroud = new ParticleHandle();
|
||||
|
||||
final EntityReference<StormCloudEntity> stormCloud = new EntityReference<>();
|
||||
|
||||
private int prevBiteTime;
|
||||
private int biteTime;
|
||||
|
||||
public static void startEncounter(World world, BlockPos pos) {
|
||||
StormCloudEntity cloud = UEntities.STORM_CLOUD.create(world);
|
||||
cloud.setPosition(pos.up(10).toCenterPos());
|
||||
cloud.setSize(1);
|
||||
cloud.cursed = true;
|
||||
world.spawnEntity(cloud);
|
||||
}
|
||||
|
||||
public SombraEntity(EntityType<SombraEntity> type, World world) {
|
||||
super(type, world);
|
||||
bossBar.setStyle(BossBar.Style.NOTCHED_10);
|
||||
|
@ -70,7 +104,13 @@ public class SombraEntity extends HostileEntity {
|
|||
public static DefaultAttributeContainer.Builder createMobAttributes() {
|
||||
return HostileEntity.createMobAttributes()
|
||||
.add(EntityAttributes.GENERIC_MAX_HEALTH, 2000)
|
||||
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 102);
|
||||
.add(EntityAttributes.GENERIC_ATTACK_KNOCKBACK, 1.5)
|
||||
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 22);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SombraEntity asEntity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -101,27 +141,37 @@ public class SombraEntity extends HostileEntity {
|
|||
|
||||
@Override
|
||||
protected void initGoals() {
|
||||
goalSelector.add(5, new WanderAroundGoal(this, 1));
|
||||
goalSelector.add(6, new LookAtEntityGoal(this, PlayerEntity.class, 8F));
|
||||
goalSelector.add(2, new LongDoorInteractGoal(this, true));
|
||||
goalSelector.add(5, new FlyGoal(this, 1));
|
||||
goalSelector.add(6, new LookAtEntityGoal(this, PlayerEntity.class, 18F));
|
||||
goalSelector.add(7, new LookAroundGoal(this));
|
||||
goalSelector.add(8, new PounceAtTargetGoal(this, 0.3f));
|
||||
goalSelector.add(8, new AttackGoal(this));
|
||||
goalSelector.add(7, new WanderAroundGoal(this, 1));
|
||||
goalSelector.add(8, new PounceAtTargetGoal(this, 1.3F));
|
||||
goalSelector.add(8, new ArenaAttackGoal<>(this));
|
||||
targetSelector.add(1, new RevengeGoal(this));
|
||||
targetSelector.add(2, new ActiveTargetGoal<>(this, PlayerEntity.class, false));
|
||||
targetSelector.add(3, new ActiveTargetGoal<>(this, MerchantEntity.class, false));
|
||||
targetSelector.add(3, new ActiveTargetGoal<>(this, IronGolemEntity.class, true));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected EntityNavigation createNavigation(World world) {
|
||||
MobNavigation nav = new MobNavigation(this, world);
|
||||
MobNavigation nav = new MobNavigation(this, world) {
|
||||
@Override
|
||||
protected PathNodeNavigator createPathNodeNavigator(int range) {
|
||||
nodeMaker = new BirdPathNodeMaker();
|
||||
nodeMaker.setCanEnterOpenDoors(true);
|
||||
return new PathNodeNavigator(nodeMaker, range);
|
||||
}
|
||||
};
|
||||
nav.setCanPathThroughDoors(true);
|
||||
nav.setCanSwim(true);
|
||||
nav.setCanEnterOpenDoors(true);
|
||||
nav.canJumpToNext(PathNodeType.UNPASSABLE_RAIL);
|
||||
return nav;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BlockPos> getHomePos() {
|
||||
return dataTracker.get(HOME_POS);
|
||||
}
|
||||
|
@ -130,9 +180,14 @@ public class SombraEntity extends HostileEntity {
|
|||
dataTracker.set(HOME_POS, Optional.of(pos));
|
||||
}
|
||||
|
||||
public float getBiteAmount(float tickDelta) {
|
||||
float progress = (MathHelper.lerp(tickDelta, prevBiteTime, biteTime) / (float)MAX_BITE_TIME);
|
||||
return 1 - Math.abs(MathHelper.sin(progress * MathHelper.PI));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
|
||||
setPersistent();
|
||||
Optional<BlockPos> homePos = getHomePos();
|
||||
|
||||
if (homePos.isEmpty() && !isRemoved()) {
|
||||
|
@ -140,59 +195,128 @@ public class SombraEntity extends HostileEntity {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.getBlockPos().getSquaredDistance(homePos.get()) > 16) {
|
||||
if (getBlockPos().getSquaredDistance(homePos.get()) > MathHelper.square(getAreaRadius())) {
|
||||
teleportTo(Vec3d.ofCenter(homePos.get()));
|
||||
setTarget(null);
|
||||
getNavigation().stop();
|
||||
}
|
||||
|
||||
prevBiteTime = biteTime;
|
||||
if (biteTime > 0) {
|
||||
biteTime--;
|
||||
}
|
||||
|
||||
super.tick();
|
||||
|
||||
addVelocity(0, 0.002F, 0);
|
||||
if (getTarget() == null && getVelocity().y < 0) {
|
||||
setVelocity(getVelocity().multiply(1, 0.4, 1));
|
||||
}
|
||||
addVelocity(0, 0.0242F, 0);
|
||||
if (isSubmergedInWater()) {
|
||||
jump();
|
||||
}
|
||||
|
||||
if (age % 50 == 0) {
|
||||
playSound(SoundEvents.ENTITY_POLAR_BEAR_AMBIENT, 3, 0.3F);
|
||||
if (random.nextInt(1200) == 0) {
|
||||
playSound(USounds.ENTITY_SOMBRA_LAUGH, 1, 1);
|
||||
getWorld().sendEntityStatus(this, BITE);
|
||||
}
|
||||
|
||||
if (age % 125 == 0) {
|
||||
if (random.nextInt(340) == 0) {
|
||||
playSound(SoundEvents.AMBIENT_CAVE.value(), 1, 0.3F);
|
||||
} else if (random.nextInt(1340) == 0) {
|
||||
playSound(USounds.ENTITY_SOMBRA_AMBIENT, 1, 1);
|
||||
}
|
||||
|
||||
if (getWorld().isClient) {
|
||||
float range = 9;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
var particle = new SphereParticleEffect(UParticles.SPHERE,
|
||||
0x222222,
|
||||
0.7F,
|
||||
(float)getWorld().getRandom().nextTriangular(2.5F, 0.7F)
|
||||
);
|
||||
getWorld().addParticle(particle,
|
||||
getWorld().getRandom().nextTriangular(getX(), range),
|
||||
getWorld().getRandom().nextTriangular(getY(), range),
|
||||
getWorld().getRandom().nextTriangular(getZ(), range),
|
||||
getWorld().getRandom().nextGaussian() / 6F,
|
||||
getWorld().getRandom().nextGaussian() / 6F,
|
||||
getWorld().getRandom().nextGaussian() / 6F
|
||||
);
|
||||
generateBodyParticles();
|
||||
} else {
|
||||
for (BlockPos p : BlockPos.iterateOutwards(getBlockPos(), 2, 1, 2)) {
|
||||
if (getWorld().getBlockState(p).getLuminance() > 3) {
|
||||
destroyLightSource(p);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 13; i++) {
|
||||
getWorld().addParticle(ParticleTypes.LARGE_SMOKE,
|
||||
getWorld().getRandom().nextTriangular(getX(), 1),
|
||||
getWorld().getRandom().nextTriangular(getY(), 1),
|
||||
getWorld().getRandom().nextTriangular(getZ(), 1),
|
||||
0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
for (BlockPos p : BlockPos.iterateRandomly(random, 3, getBlockPos(), 20)) {
|
||||
CrystalShardsEntity.infestBlock((ServerWorld)getWorld(), p);
|
||||
}
|
||||
|
||||
if (getTarget() == null && getNavigation().isIdle()) {
|
||||
getNavigation().startMovingTo(homePos.get().getX(), homePos.get().getY() + 10, homePos.get().getZ(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
getWorld().getOtherEntities(this, this.getBoundingBox().expand(5), EntityPredicates.VALID_LIVING_ENTITY).forEach(target -> {
|
||||
((LivingEntity)target).addStatusEffect(new StatusEffectInstance(StatusEffects.BLINDNESS, 100, 1));
|
||||
});
|
||||
getHomePos().ifPresent(this::generateArenaEffects);
|
||||
}
|
||||
|
||||
protected void applyAreaEffects(Entity target) {
|
||||
if (this.age % 150 == 0) {
|
||||
target.playSound(
|
||||
random.nextInt(30) == 0 ? USounds.ENTITY_SOMBRA_AMBIENT
|
||||
: random.nextInt(10) == 0 ? USounds.Vanilla.ENTITY_GHAST_AMBIENT
|
||||
: USounds.Vanilla.AMBIENT_CAVE.value(),
|
||||
(float)random.nextTriangular(1, 0.2F),
|
||||
(float)random.nextTriangular(0.3F, 0.2F)
|
||||
);
|
||||
}
|
||||
|
||||
((LivingEntity)target).addStatusEffect(new StatusEffectInstance(StatusEffects.BLINDNESS, 26, 0, true, false));
|
||||
((LivingEntity)target).addStatusEffect(new StatusEffectInstance(StatusEffects.DARKNESS, 26, 0, true, false));
|
||||
|
||||
if (getTarget() == null && target instanceof PlayerEntity player && player.distanceTo(this) < getAreaRadius() / 2F) {
|
||||
setTarget(player);
|
||||
if (teleportTo(target.getPos())) {
|
||||
setPosition(getPos().add(0, 4, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void destroyLightSource(BlockPos pos) {
|
||||
getWorld().breakBlock(pos, true);
|
||||
playSound(USounds.ENTITY_SOMBRA_SNICKER, 1, 1);
|
||||
}
|
||||
|
||||
protected void generateBodyParticles() {
|
||||
for (int i = 0; i < 23; i++) {
|
||||
getWorld().addParticle(ParticleTypes.LARGE_SMOKE,
|
||||
random.nextTriangular(getX(), 8),
|
||||
random.nextTriangular(getY(), 1),
|
||||
random.nextTriangular(getZ(), 8),
|
||||
0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateArenaEffects(BlockPos home) {
|
||||
if (getWorld().isClient()) {
|
||||
Stream.concat(
|
||||
new Sphere(false, getAreaRadius()).translate(home).randomPoints(random).filter(this::isSurfaceBlock).limit(80),
|
||||
new Sphere(true, getAreaRadius()).translate(home).randomPoints(random).filter(this::isSurfaceBlock).limit(30))
|
||||
.forEach(pos -> {
|
||||
ParticleEffect type = random.nextInt(3) < 1 ? ParticleTypes.LARGE_SMOKE : ParticleTypes.SOUL_FIRE_FLAME;
|
||||
ParticleUtils.spawnParticle(getWorld(), type, pos, Vec3d.ZERO);
|
||||
ParticleUtils.spawnParticle(getWorld(), type, pos, pos.subtract(getPos()).add(0, 0.1, 0).multiply(-0.013));
|
||||
});
|
||||
|
||||
shroud.update(getUuid(), this, spawner -> {
|
||||
var radius = getAreaRadius();
|
||||
var center = home.toCenterPos();
|
||||
spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, 0xFF000000, 1, radius - 0.2F), center, Vec3d.ZERO);
|
||||
}).ifPresent(attachment -> {
|
||||
attachment.setAttribute(Attachment.ATTR_BOUND, 1);
|
||||
attachment.setAttribute(Attachment.ATTR_COLOR, 0xFF000000);
|
||||
attachment.setAttribute(Attachment.ATTR_OPACITY, 2.5F);
|
||||
});
|
||||
} else {
|
||||
for (Entity target : VecHelper.findInRange(this, getWorld(), home.toCenterPos(), getAreaRadius() - 0.2F, EFFECT_TARGET_PREDICATE)) {
|
||||
applyAreaEffects(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSurfaceBlock(Vec3d pos) {
|
||||
BlockPos bPos = BlockPos.ofFloored(pos);
|
||||
return getWorld().isAir(bPos) && !getWorld().isAir(bPos.down());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -207,21 +331,6 @@ public class SombraEntity extends HostileEntity {
|
|||
return distance < d * d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pushAway(Entity entity) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tickCramming() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleFallDamage(float distance, float damageMultiplier, DamageSource cause) {
|
||||
return false;
|
||||
|
@ -232,6 +341,7 @@ public class SombraEntity extends HostileEntity {
|
|||
if (source.getAttacker() instanceof PlayerEntity player) {
|
||||
if (AmuletSelectors.ALICORN_AMULET.test(player)) {
|
||||
if (!getWorld().isClient) {
|
||||
playSound(USounds.ENTITY_SOMBRA_SNICKER, 1, 1);
|
||||
player.sendMessage(Text.translatable("entity.unicopia.sombra.taunt"));
|
||||
}
|
||||
}
|
||||
|
@ -249,10 +359,47 @@ public class SombraEntity extends HostileEntity {
|
|||
return damaged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(DamageSource damageSource) {
|
||||
if (!dead) {
|
||||
stormCloud.ifPresent(getWorld(), cloud -> {
|
||||
cloud.setStormTicks(0);
|
||||
cloud.setDissipating(true);
|
||||
});
|
||||
stormCloud.set(null);
|
||||
getHomePos().ifPresent(home -> {
|
||||
VecHelper.findInRange(this, getWorld(), home.toCenterPos(), getAreaRadius() - 0.2F, e -> e instanceof CrystalShardsEntity).forEach(e -> {
|
||||
((CrystalShardsEntity)e).setDecaying(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
super.onDeath(damageSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fall(double y, boolean onGroundIn, BlockState state, BlockPos pos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(LivingEntity target) {
|
||||
return super.canTarget(target) && getHomePos().filter(home -> target.getPos().isInRange(home.toCenterPos(), getAreaRadius())).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAttack(Entity target) {
|
||||
getWorld().sendEntityStatus(this, BITE);
|
||||
return super.tryAttack(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleStatus(byte status) {
|
||||
if (status == BITE) {
|
||||
biteTime = MAX_BITE_TIME;
|
||||
} else {
|
||||
super.handleStatus(status);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean teleportRandomly(int maxDistance) {
|
||||
if (getWorld().isClient() || !isAlive()) {
|
||||
return false;
|
||||
|
@ -260,7 +407,8 @@ public class SombraEntity extends HostileEntity {
|
|||
return teleportTo(getPos().add(VecHelper.supply(() -> random.nextTriangular(0, maxDistance))));
|
||||
}
|
||||
|
||||
private boolean teleportTo(Vec3d destination) {
|
||||
@Override
|
||||
public boolean teleportTo(Vec3d destination) {
|
||||
Vec3d oldPos = getPos();
|
||||
if (canTeleportTo(destination) && teleport(destination.x, destination.y, destination.z, true)) {
|
||||
getWorld().emitGameEvent(GameEvent.TELEPORT, oldPos, GameEvent.Emitter.of(this));
|
||||
|
@ -309,6 +457,7 @@ public class SombraEntity extends HostileEntity {
|
|||
getHomePos().map(NbtHelper::fromBlockPos).ifPresent(pos -> {
|
||||
nbt.put("homePos", pos);
|
||||
});
|
||||
nbt.put("cloud", stormCloud.toNBT());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -320,5 +469,6 @@ public class SombraEntity extends HostileEntity {
|
|||
if (hasCustomName()) {
|
||||
bossBar.setName(getDisplayName());
|
||||
}
|
||||
stormCloud.fromNBT(nbt.getCompound("cloud"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
package com.minelittlepony.unicopia.entity;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.minelittlepony.unicopia.USounds;
|
||||
import com.minelittlepony.unicopia.particle.UParticles;
|
||||
import com.minelittlepony.unicopia.server.world.WeatherConditions;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.LightningEntity;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.data.DataTracker;
|
||||
import net.minecraft.entity.data.TrackedData;
|
||||
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.predicate.entity.EntityPredicates;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.Heightmap.Type;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class StormCloudEntity extends Entity {
|
||||
private static final TrackedData<Integer> CLEAR_TICKS = DataTracker.registerData(StormCloudEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
||||
private static final TrackedData<Integer> STORM_TICKS = DataTracker.registerData(StormCloudEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
||||
private static final TrackedData<Float> TARGET_SIZE = DataTracker.registerData(StormCloudEntity.class, TrackedDataHandlerRegistry.FLOAT);
|
||||
private static final TrackedData<Boolean> DISSIPATING = DataTracker.registerData(StormCloudEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
|
||||
|
||||
private float prevSize;
|
||||
private float currentSize;
|
||||
|
||||
public boolean cursed;
|
||||
|
||||
private int phase;
|
||||
private int nextPhase;
|
||||
|
||||
public StormCloudEntity(EntityType<StormCloudEntity> type, World world) {
|
||||
super(type, world);
|
||||
setSize(1 + random.nextInt(4));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataTracker() {
|
||||
dataTracker.startTracking(STORM_TICKS, 0);
|
||||
dataTracker.startTracking(CLEAR_TICKS, 0);
|
||||
dataTracker.startTracking(TARGET_SIZE, 1F);
|
||||
dataTracker.startTracking(DISSIPATING, false);
|
||||
}
|
||||
|
||||
public boolean isStormy() {
|
||||
return getStormTicks() != 0;
|
||||
}
|
||||
|
||||
public int getStormTicks() {
|
||||
return dataTracker.get(STORM_TICKS);
|
||||
}
|
||||
|
||||
public void setStormTicks(int stormTicks) {
|
||||
dataTracker.set(STORM_TICKS, stormTicks);
|
||||
setClearTicks(stormTicks);
|
||||
}
|
||||
|
||||
public void setDissipating(boolean dissipating) {
|
||||
dataTracker.set(DISSIPATING, dissipating);
|
||||
}
|
||||
|
||||
public boolean isDissipating() {
|
||||
return dataTracker.get(DISSIPATING);
|
||||
}
|
||||
|
||||
private void setClearTicks(int clearTicks) {
|
||||
dataTracker.set(CLEAR_TICKS, clearTicks);
|
||||
}
|
||||
|
||||
private int getClearTicks() {
|
||||
return dataTracker.get(CLEAR_TICKS);
|
||||
}
|
||||
|
||||
public boolean tickClear() {
|
||||
int clearTicks = getClearTicks();
|
||||
if (clearTicks > 0) {
|
||||
setClearTicks(--clearTicks);
|
||||
}
|
||||
return clearTicks > 0;
|
||||
}
|
||||
|
||||
public float getSize(float tickDelta) {
|
||||
return MathHelper.clamp(MathHelper.lerp(tickDelta, prevSize, currentSize), 1, 30);
|
||||
}
|
||||
|
||||
public void setSize(float size) {
|
||||
dataTracker.set(TARGET_SIZE, size);
|
||||
}
|
||||
|
||||
public int getSizeInBlocks() {
|
||||
return (int)(getWidth() * (getSize(1) / 15F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
setFireTicks(1);
|
||||
|
||||
|
||||
prevSize = currentSize;
|
||||
float targetSize = dataTracker.get(TARGET_SIZE);
|
||||
if (currentSize != targetSize) {
|
||||
float sizeDifference = (dataTracker.get(TARGET_SIZE) - currentSize);
|
||||
currentSize = Math.abs(sizeDifference) < 0.01F ? targetSize : currentSize + (sizeDifference * 0.2F);
|
||||
}
|
||||
|
||||
if (isStormy()) {
|
||||
int stormTicks = getStormTicks();
|
||||
if (stormTicks > 0) {
|
||||
setStormTicks(stormTicks - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (isLogicalSideForUpdatingMovement()) {
|
||||
float groundY = getWorld().getTopY(Type.MOTION_BLOCKING_NO_LEAVES, (int)getX(), (int)getZ());
|
||||
float cloudY = (float)getY() - (isStormy() ? 20 : 90);
|
||||
|
||||
addVelocity(0, 0.03F * (groundY - cloudY), 0);
|
||||
|
||||
if (!cursed && !isStormy()) {
|
||||
Vec3d wind = WeatherConditions.get(getWorld()).getWindDirection();
|
||||
addVelocity(wind.x * 0.001F, 0, wind.z * 0.001F);
|
||||
}
|
||||
|
||||
Vec3d velocity = getVelocity();
|
||||
setPosition(getPos().add(velocity.multiply(0.7F)));
|
||||
setVelocity(velocity.multiply(0.9F));
|
||||
|
||||
float randomYaw = random.nextFloat() * 360;
|
||||
Vec3d randomVelocity = Vec3d.fromPolar(0, randomYaw).multiply(0.002);
|
||||
|
||||
for (var cloud : getWorld().getEntitiesByClass(StormCloudEntity.class, getBoundingBox(), EntityPredicates.VALID_ENTITY)) {
|
||||
if (cloud != this) {
|
||||
cloud.addVelocity(randomVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.tick();
|
||||
|
||||
float size = getSize(1);
|
||||
|
||||
if (getWorld().isClient()) {
|
||||
if (isStormy()) {
|
||||
WeatherConditions.get(getWorld()).addStorm(this);
|
||||
|
||||
float sizeInBlocks = getSizeInBlocks();
|
||||
int area = (int)MathHelper.square(sizeInBlocks);
|
||||
sizeInBlocks /= getWidth();
|
||||
for (int i = 0; i < area; i++) {
|
||||
float x = (float)getParticleX(sizeInBlocks);
|
||||
float z = (float)getParticleZ(sizeInBlocks);
|
||||
|
||||
getWorld().addParticle(UParticles.RAIN_DROPS, x, getY(), z, 0, -0.2F, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isStormy() && !cursed && size > 10) {
|
||||
if (random.nextInt(1 + (int)(4000 / size)) == 0) {
|
||||
split(random.nextBetween(1, 3));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSize == targetSize) {
|
||||
if (isDissipating()) {
|
||||
if (size < 2) {
|
||||
kill();
|
||||
} else {
|
||||
if (random.nextInt(4) == 0) {
|
||||
split(2);
|
||||
} else {
|
||||
setSize(size - 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (size < 30) {
|
||||
if (cursed) {
|
||||
setSize(size + 1);
|
||||
setStormTicks(1);
|
||||
spawnLightningStrike(getBlockPos(), false, false);
|
||||
playSound(USounds.AMBIENT_WIND_GUST, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (size >= 30) {
|
||||
if (cursed) {
|
||||
setStormTicks(-1);
|
||||
|
||||
if (nextPhase-- == 0) {
|
||||
phase++;
|
||||
nextPhase = random.nextBetween(17, 120);
|
||||
|
||||
if (phase == 1) {
|
||||
playSound(USounds.ENTITY_SOMBRA_SNICKER, 10, 1);
|
||||
}
|
||||
|
||||
if (++phase >= 7) {
|
||||
pickRandomPoints(13, pos -> spawnLightningStrike(pos, true, false));
|
||||
|
||||
cursed = false;
|
||||
SombraEntity sombra = UEntities.SOMBRA.create(getWorld());
|
||||
sombra.setPosition(getPos());
|
||||
sombra.setHomePos(getWorld().getTopPosition(Type.MOTION_BLOCKING_NO_LEAVES, getBlockPos()));
|
||||
sombra.stormCloud.set(this);
|
||||
getWorld().spawnEntity(sombra);
|
||||
|
||||
playSound(USounds.ENTITY_SOMBRA_LAUGH, 10, 1);
|
||||
} else {
|
||||
pickRandomPoints(3, pos -> spawnLightningStrike(pos, true, false));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isStormy() && !tickClear()) {
|
||||
setStormTicks(random.nextBetween(30, 120));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isStormy() && age % 170 == 0) {
|
||||
pickRandomPoints(3, pos -> spawnLightningStrike(pos, false, random.nextInt(30) == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pickRandomPoints(int count, Consumer<BlockPos> action) {
|
||||
BlockPos.iterateRandomly(random, 3, getBlockPos(), getSizeInBlocks()).forEach(pos -> {
|
||||
|
||||
BlockPos.Mutable mutable = new BlockPos.Mutable();
|
||||
mutable.set(pos);
|
||||
while (getWorld().isInBuildLimit(mutable) && getWorld().isAir(mutable)) {
|
||||
mutable.move(Direction.DOWN);
|
||||
}
|
||||
while (getWorld().isInBuildLimit(mutable) && !getWorld().isAir(mutable)) {
|
||||
mutable.move(Direction.UP);
|
||||
}
|
||||
mutable.move(Direction.DOWN);
|
||||
|
||||
action.accept(pos);
|
||||
});
|
||||
}
|
||||
|
||||
private void spawnLightningStrike(BlockPos pos, boolean cosmetic, boolean infect) {
|
||||
if (infect) {
|
||||
if (!CrystalShardsEntity.infestBlock((ServerWorld)getWorld(), pos)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
LightningEntity lightning = EntityType.LIGHTNING_BOLT.create(getWorld());
|
||||
lightning.refreshPositionAfterTeleport(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
|
||||
lightning.setCosmetic(cosmetic);
|
||||
getWorld().spawnEntity(lightning);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDamaged(DamageSource damageSource) {
|
||||
if (random.nextInt(35) == 0) {
|
||||
split(2 + random.nextInt(4));
|
||||
}
|
||||
}
|
||||
|
||||
public void split(int splitCount) {
|
||||
Vec3d center = getPos();
|
||||
|
||||
float totalAngle = (360F / splitCount) + ((float)random.nextGaussian() * 360F);
|
||||
float size = getSize(1) / splitCount;
|
||||
int stormTicks = getStormTicks() / splitCount;
|
||||
|
||||
if (size < 1) {
|
||||
return;
|
||||
}
|
||||
discard();
|
||||
|
||||
for (int i = 0; i < splitCount; i++) {
|
||||
StormCloudEntity lump = (StormCloudEntity)getType().create(getWorld());
|
||||
lump.setSize(size);
|
||||
lump.setStormTicks(stormTicks);
|
||||
lump.setPosition(center);
|
||||
lump.setDissipating(isDissipating());
|
||||
lump.setVelocity(Vec3d.fromPolar(0, totalAngle * i).normalize().multiply(1.8F));
|
||||
|
||||
getWorld().spawnEntity(lump);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleStatus(byte status) {
|
||||
super.handleStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeCustomDataToNbt(NbtCompound nbt) {
|
||||
nbt.putInt("stormTicks", getStormTicks());
|
||||
nbt.putInt("clearTicks", getClearTicks());
|
||||
nbt.putFloat("size", getSize(1));
|
||||
nbt.putBoolean("cursed", cursed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readCustomDataFromNbt(NbtCompound nbt) {
|
||||
setStormTicks(nbt.getInt("stormTicks"));
|
||||
setClearTicks(nbt.getInt("clearTicks"));
|
||||
setSize(currentSize = nbt.getFloat("size"));
|
||||
cursed = nbt.getBoolean("cursed");
|
||||
}
|
||||
}
|
|
@ -48,7 +48,13 @@ public interface UEntities {
|
|||
.dimensions(EntityDimensions.fixed(0.9F, 0.5F)));
|
||||
EntityType<SombraEntity> SOMBRA = register("sombra", FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, SombraEntity::new)
|
||||
.trackRangeBlocks(200)
|
||||
.dimensions(EntityDimensions.fixed(1F, 2F)));
|
||||
EntityType<CrystalShardsEntity> CRYSTAL_SHARDS = register("crystal_shards", FabricEntityTypeBuilder.create(SpawnGroup.MISC, CrystalShardsEntity::new)
|
||||
.trackRangeBlocks(100)
|
||||
.dimensions(EntityDimensions.fixed(1F, 1F)));
|
||||
EntityType<StormCloudEntity> STORM_CLOUD = register("storm_cloud", FabricEntityTypeBuilder.create(SpawnGroup.MISC, StormCloudEntity::new)
|
||||
.trackRangeBlocks(200)
|
||||
.dimensions(EntityDimensions.fixed(20F, 20F)));
|
||||
EntityType<AirBalloonEntity> AIR_BALLOON = register("air_balloon", FabricEntityTypeBuilder.create(SpawnGroup.MISC, AirBalloonEntity::new)
|
||||
.trackRangeBlocks(1000)
|
||||
.dimensions(EntityDimensions.changing(2.5F, 0.1F)));
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.minelittlepony.unicopia.entity.ai;
|
||||
|
||||
import com.minelittlepony.unicopia.entity.ArenaCombatant;
|
||||
|
||||
import net.minecraft.entity.ai.goal.AttackGoal;
|
||||
import net.minecraft.entity.ai.pathing.Path;
|
||||
import net.minecraft.entity.mob.MobEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
public class ArenaAttackGoal<E extends MobEntity & ArenaCombatant> extends AttackGoal {
|
||||
|
||||
private final E combatant;
|
||||
|
||||
public ArenaAttackGoal(E mob) {
|
||||
super(mob);
|
||||
this.combatant = mob;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canStart() {
|
||||
return combatant.getHomePos().isPresent() && super.canStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldContinue() {
|
||||
return combatant.getHomePos().filter(this::isInArena).isPresent() && super.shouldContinue();
|
||||
}
|
||||
|
||||
private boolean isInArena(BlockPos homePos) {
|
||||
return combatant.getBlockPos().isWithinDistance(homePos, combatant.getAreaRadius());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
combatant.setTarget(null);
|
||||
combatant.getHomePos().ifPresent(home -> {
|
||||
|
||||
Path path = combatant.getNavigation().findPathTo(home, 2, (int)combatant.getAreaRadius() * 2);
|
||||
if (path != null) {
|
||||
combatant.getNavigation().startMovingAlong(path, combatant.getMovementSpeed() * 2F);
|
||||
} else {
|
||||
combatant.teleportTo(home.toCenterPos());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ public interface MeteorlogicalUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
final float skyAngle = getSkyAngle(entity.getWorld());
|
||||
float playerYaw = MathHelper.wrapDegrees(entity.getHeadYaw());
|
||||
float playerAngle = (-entity.getPitch(1) / 90F) / 2F;
|
||||
|
|
|
@ -586,6 +586,10 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
|
|||
}
|
||||
|
||||
sendCapabilities();
|
||||
|
||||
//if (!isClient()) {
|
||||
// CrystalShardsEntity.infestBlock((ServerWorld)asWorld(), entity.getBlockPos().down());
|
||||
//}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -257,6 +257,11 @@ public class AlicornAmuletItem extends AmuletItem implements ItemTracker.Trackab
|
|||
player.getHungerManager().addExhaustion(90F);
|
||||
float healthDrop = MathHelper.clamp(player.getMaxHealth() - player.getHealth(), 2, 5);
|
||||
player.damage(pony.damageOf(UDamageTypes.ALICORN_AMULET), healthDrop);
|
||||
|
||||
if (player.getHealth() < 2) {
|
||||
stack.decrement(1);
|
||||
SombraEntity.startEncounter(player.getWorld(), player.getBlockPos());
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
@ -8,6 +8,7 @@ import java.util.UUID;
|
|||
import java.util.WeakHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.minelittlepony.unicopia.EntityConvertable;
|
||||
import com.minelittlepony.unicopia.ability.magic.Caster;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
|
@ -59,8 +60,8 @@ public class ParticleHandle {
|
|||
Entry p = SPAWNED_PARTICLES.computeIfAbsent(id, i -> new WeakHashMap<>()).computeIfAbsent(partName, i -> {
|
||||
constructor.accept((effect, pos, vel) -> {
|
||||
pp = MinecraftClient.getInstance().particleManager.addParticle(effect, pos.x, pos.y, pos.z, vel.x, vel.y, vel.z);
|
||||
if (pp instanceof Attachment && source instanceof Caster<?>) {
|
||||
((Attachment) pp).attach(new Link(id, (Caster<?>)source));
|
||||
if (pp instanceof Attachment) {
|
||||
((Attachment) pp).attach(new Link(id, source));
|
||||
}
|
||||
});
|
||||
return new Entry(new WeakReference<>(MinecraftClient.getInstance().world), new WeakReference<>(pp));
|
||||
|
@ -102,16 +103,16 @@ public class ParticleHandle {
|
|||
}
|
||||
|
||||
public static final class Link {
|
||||
private Optional<WeakReference<Caster<?>>> caster = Optional.empty();
|
||||
private Optional<WeakReference<EntityConvertable<?>>> caster = Optional.empty();
|
||||
private UUID effect;
|
||||
|
||||
private Link(UUID effect, Caster<?> caster) {
|
||||
private Link(UUID effect, EntityConvertable<?> caster) {
|
||||
this.caster = Optional.of(new WeakReference<>(caster));
|
||||
this.effect = effect;
|
||||
}
|
||||
|
||||
public Optional<Caster<?>> get() {
|
||||
caster = caster.filter(r -> r.get() != null && r.get().getSpellSlot().contains(effect) && r.get().asEntity().isAlive());
|
||||
public Optional<EntityConvertable<?>> get() {
|
||||
caster = caster.filter(r -> r.get() != null && (!(r.get() instanceof Caster<?> c) || c.getSpellSlot().contains(effect)) && r.get().asEntity().isAlive());
|
||||
return caster.map(WeakReference::get);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
package com.minelittlepony.unicopia.server.world;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.minelittlepony.unicopia.Unicopia;
|
||||
import com.minelittlepony.unicopia.entity.StormCloudEntity;
|
||||
import com.minelittlepony.unicopia.entity.player.MeteorlogicalUtil;
|
||||
import com.minelittlepony.unicopia.util.Tickable;
|
||||
|
||||
|
@ -55,6 +61,8 @@ public class WeatherConditions extends PersistentState implements Tickable {
|
|||
|
||||
private boolean prevDayState;
|
||||
|
||||
private Map<UUID, Storm> storms = new HashMap<>();
|
||||
|
||||
private WeatherConditions(World world, NbtCompound compound) {
|
||||
this(world);
|
||||
windYaw = compound.getFloat("windYaw");
|
||||
|
@ -68,6 +76,19 @@ public class WeatherConditions extends PersistentState implements Tickable {
|
|||
this.world = world;
|
||||
}
|
||||
|
||||
public void addStorm(StormCloudEntity cloud) {
|
||||
synchronized (storms) {
|
||||
storms.computeIfAbsent(cloud.getUuid(), id -> new Storm(cloud));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInRangeOfStorm(BlockPos pos) {
|
||||
synchronized (storms) {
|
||||
storms.values().removeIf(Storm::shouldRemove);
|
||||
return storms.values().stream().anyMatch(storm -> storm.inRange(pos));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (interpolation < maxInterpolation) {
|
||||
|
@ -107,6 +128,24 @@ public class WeatherConditions extends PersistentState implements Tickable {
|
|||
return compound;
|
||||
}
|
||||
|
||||
private class Storm {
|
||||
private final WeakReference<StormCloudEntity> cloud;
|
||||
|
||||
public Storm(StormCloudEntity cloud) {
|
||||
this.cloud = new WeakReference<>(cloud);
|
||||
}
|
||||
|
||||
public boolean inRange(BlockPos pos) {
|
||||
final StormCloudEntity cloud = this.cloud.get();
|
||||
return cloud != null && cloud.getBlockPos().isWithinDistance(pos, cloud.getSizeInBlocks());
|
||||
}
|
||||
|
||||
public boolean shouldRemove() {
|
||||
final StormCloudEntity cloud = this.cloud.get();
|
||||
return cloud == null || cloud.isRemoved() || !cloud.isStormy();
|
||||
}
|
||||
}
|
||||
|
||||
public static Vec3d getAirflow(BlockPos pos, World world) {
|
||||
BlockPos.Mutable probedPosition = new BlockPos.Mutable();
|
||||
|
||||
|
|
|
@ -228,6 +228,24 @@
|
|||
],
|
||||
"subtitle": "unicopia.subtitle.entity.twittermite.hurt"
|
||||
},
|
||||
"entity.sombra.ambient": {
|
||||
"subtitle": "unicopia.subtitle.sombra.ambient",
|
||||
"sounds": [
|
||||
"unicopia:entity/sombra/sombra_idle"
|
||||
]
|
||||
},
|
||||
"entity.sombra.laugh": {
|
||||
"subtitle": "unicopia.subtitle.sombra.laugh",
|
||||
"sounds": [
|
||||
"unicopia:entity/sombra/sombra_laugh"
|
||||
]
|
||||
},
|
||||
"entity.sombra.snicker": {
|
||||
"subtitle": "unicopia.subtitle.sombra.snicker",
|
||||
"sounds": [
|
||||
"unicopia:entity/sombra/sombra_snicker"
|
||||
]
|
||||
},
|
||||
"entity.jar.throw": {
|
||||
"sounds": [
|
||||
"random/bow"
|
||||
|
|
After Width: | Height: | Size: 5 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 996 B |
After Width: | Height: | Size: 1.7 KiB |
BIN
tom.png
Normal file
After Width: | Height: | Size: 347 B |