diff --git a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java index a00d7111..5cafbb74 100644 --- a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java +++ b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java @@ -6,8 +6,11 @@ import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Spell; import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.item.enchantment.UEnchantments; +import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; public interface EquinePredicates { @@ -23,6 +26,8 @@ public interface EquinePredicates { Predicate IS_CASTER = e -> !e.removed && (e instanceof Caster || PLAYER_UNICORN.test(e)); + Predicate HAS_WANT_IT_NEED_IT = e -> EnchantmentHelper.getEquipmentLevel(UEnchantments.WANT_IT_NEED_IT, e) > 0; + static Predicate carryingSpell(Class type) { return IS_PLAYER.and(entity -> Pony.of((PlayerEntity)entity).getSpellOrEmpty(type, false).isPresent()); } diff --git a/src/main/java/com/minelittlepony/unicopia/UEntities.java b/src/main/java/com/minelittlepony/unicopia/UEntities.java index 434ddf0a..afb23767 100644 --- a/src/main/java/com/minelittlepony/unicopia/UEntities.java +++ b/src/main/java/com/minelittlepony/unicopia/UEntities.java @@ -1,5 +1,6 @@ package com.minelittlepony.unicopia; +import com.minelittlepony.unicopia.entity.FloatingArtefactEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; @@ -16,6 +17,9 @@ public interface UEntities { .trackRangeBlocks(100) .trackedUpdateRate(2) .dimensions(EntityDimensions.fixed(0.25F, 0.25F))); + EntityType FLOATING_ARTEFACT = register("floating_artefact", FabricEntityTypeBuilder.create(SpawnGroup.MISC, FloatingArtefactEntity::new) + .trackRangeBlocks(200) + .dimensions(EntityDimensions.fixed(1, 1))); static EntityType register(String name, FabricEntityTypeBuilder builder) { EntityType type = builder.build(); diff --git a/src/main/java/com/minelittlepony/unicopia/UTags.java b/src/main/java/com/minelittlepony/unicopia/UTags.java index fefccd37..24cebd7c 100644 --- a/src/main/java/com/minelittlepony/unicopia/UTags.java +++ b/src/main/java/com/minelittlepony/unicopia/UTags.java @@ -17,6 +17,9 @@ public interface UTags { Tag FRAGILE = block("fragile"); Tag INTERESTING = block("interesting"); + Tag CRYSTAL_HEART_BASE = block("crystal_heart_base"); + Tag CRYSTAL_HEART_ORNAMENT = block("crystal_heart_ornament"); + static Tag item(String name) { return TagRegistry.item(new Identifier("unicopia", name)); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 72b512eb..299cdcf0 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.client.particle.RainboomParticle; import com.minelittlepony.unicopia.client.particle.RainbowTrailParticle; import com.minelittlepony.unicopia.client.particle.RaindropsParticle; import com.minelittlepony.unicopia.client.particle.SphereParticle; +import com.minelittlepony.unicopia.client.render.FloatingArtefactEntityRenderer; import com.minelittlepony.unicopia.item.ChameleonItem; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.particle.UParticles; @@ -44,6 +45,7 @@ public interface URenderers { ParticleFactoryRegistry.getInstance().register(UParticles.GROUND_POUND, GroundPoundParticle::new); EntityRendererRegistry.INSTANCE.register(UEntities.THROWN_ITEM, (manager, context) -> new FlyingItemEntityRenderer<>(manager, context.getItemRenderer())); + EntityRendererRegistry.INSTANCE.register(UEntities.FLOATING_ARTEFACT, FloatingArtefactEntityRenderer::new); ColorProviderRegistry.ITEM.register((stack, i) -> i > 0 ? -1 : ((DyeableItem)stack.getItem()).getColor(stack), UItems.FRIENDSHIP_BRACELET); BuiltinItemRendererRegistry.INSTANCE.register(UItems.FILLED_JAR, (stack, mode, matrices, vertexConsumers, light, overlay) -> { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/FloatingArtefactEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/FloatingArtefactEntityRenderer.java new file mode 100644 index 00000000..e8c4b419 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/FloatingArtefactEntityRenderer.java @@ -0,0 +1,63 @@ +package com.minelittlepony.unicopia.client.render; + +import com.minelittlepony.unicopia.entity.FloatingArtefactEntity; +import com.minelittlepony.unicopia.item.UItems; + +import net.fabricmc.fabric.api.client.rendereregistry.v1.EntityRendererRegistry; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.json.ModelTransformation; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.client.util.math.Vector3f; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +public class FloatingArtefactEntityRenderer extends EntityRenderer { + + private final ItemRenderer itemRenderer; + + public FloatingArtefactEntityRenderer(EntityRenderDispatcher manager, EntityRendererRegistry.Context context) { + super(manager); + itemRenderer = context.getItemRenderer(); + } + + @Override + public void render(FloatingArtefactEntity entity, float yaw, float timeDelta, MatrixStack transforms, VertexConsumerProvider renderContext, int lightUv) { + + ItemStack stack = entity.getStack(); + + if (stack.isEmpty()) { + stack = UItems.EMPTY_JAR.getDefaultStack(); + } + + final BakedModel model = this.itemRenderer.getHeldItemModel(stack, entity.world, null); + + final float variance = 0.25F; + final float verticalOffset = entity.getVerticalOffset(timeDelta); + final float modelScaleY = model.getTransformation().getTransformation(ModelTransformation.Mode.GROUND).scale.getY(); + + float scale = 1.6F; + + transforms.push(); + transforms.scale(scale, scale, scale); + transforms.translate(0, verticalOffset + variance * modelScaleY, 0); + transforms.multiply(Vector3f.POSITIVE_Y.getRadialQuaternion(entity.getRotation(timeDelta))); + + itemRenderer.renderItem(stack, ModelTransformation.Mode.GROUND, false, transforms, renderContext, lightUv, OverlayTexture.DEFAULT_UV, model); + + transforms.pop(); + + super.render(entity, yaw, timeDelta, transforms, renderContext, lightUv); + } + + @Override + public Identifier getTexture(FloatingArtefactEntity entity) { + return SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE; + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java index f02aee7c..5c8836a4 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java @@ -6,11 +6,13 @@ import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Levelled; import com.minelittlepony.unicopia.ability.magic.Spell; import com.minelittlepony.unicopia.ability.magic.spell.SpellRegistry; +import com.minelittlepony.unicopia.entity.ai.BreakHeartGoal; import com.minelittlepony.unicopia.entity.ai.WantItNeedItTargetGoal; import com.minelittlepony.unicopia.entity.ai.WantItTakeItGoal; import com.minelittlepony.unicopia.entity.player.PlayerAttributes; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnGroup; import net.minecraft.entity.ai.goal.GoalSelector; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; @@ -37,6 +39,9 @@ public class Creature extends Living { public void initAi(GoalSelector goals, GoalSelector targets) { targets.add(1, new WantItNeedItTargetGoal((MobEntity)entity)); goals.add(1, new WantItTakeItGoal((MobEntity)entity)); + if (entity.getType().getSpawnGroup() == SpawnGroup.MONSTER) { + goals.add(2, new BreakHeartGoal((MobEntity)entity)); + } } public static void registerAttributes(DefaultAttributeContainer.Builder builder) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/FloatingArtefactEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/FloatingArtefactEntity.java new file mode 100644 index 00000000..3e920ee8 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/FloatingArtefactEntity.java @@ -0,0 +1,172 @@ +package com.minelittlepony.unicopia.entity; + +import com.minelittlepony.unicopia.item.UItems; +import com.minelittlepony.unicopia.network.Channel; +import com.minelittlepony.unicopia.network.MsgSpawnProjectile; + +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.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Packet; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class FloatingArtefactEntity extends Entity { + + private static final TrackedData ITEM = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.ITEM_STACK); + private static final TrackedData STATE = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.BYTE); + + private int age; + private float health = 1; + public final float positionSeed; + + public FloatingArtefactEntity(EntityType entityType, World world) { + super(entityType, world); + + positionSeed = (float)(Math.random() * Math.PI * 2); + } + + @Override + protected void initDataTracker() { + dataTracker.startTracking(ITEM, ItemStack.EMPTY); + dataTracker.startTracking(STATE, (byte)0); + } + + public ItemStack getStack() { + return dataTracker.get(ITEM); + } + + public void setStack(ItemStack stack) { + dataTracker.set(ITEM, stack); + } + + public State getState() { + return State.valueOf(dataTracker.get(STATE)); + } + + public void setState(State state) { + dataTracker.set(STATE, (byte)state.ordinal()); + } + + public void addSpin(int spin) { + if (age != -32768) { + age += spin; + } + } + + @Override + public void tick() { + Vec3d pos = Vec3d.ofBottomCenter(getBlockPos()); + + setPos(pos.x, pos.y, pos.z); + + super.tick(); + + ItemStack stack = getStack(); + + if (stack.isEmpty()) { + setStack(UItems.EMPTY_JAR.getDefaultStack()); + } + + if (stack.getItem() instanceof Artifact) { + ((Artifact)stack.getItem()).onArtifactTick(this); + } + + if (world.getTime() % 80 == 0) { + State state = getState(); + playSound(SoundEvents.BLOCK_BEACON_AMBIENT, state.getVolume(), state.getPitch()); + } + + addSpin(1); + } + + public float getVerticalOffset(float tickDelta) { + return MathHelper.sin((age + tickDelta) / 10F + positionSeed) * 0.025F + 0.05F; + } + + public float getRotation(float tickDelta) { + return (age + tickDelta) / 20 + positionSeed; + } + + @Override + protected void readCustomDataFromTag(CompoundTag compound) { + ItemStack itemStack = ItemStack.fromTag(compound.getCompound("Item")); + setStack(itemStack); + setState(State.valueOf(compound.getInt("State"))); + } + + @Override + protected void writeCustomDataToTag(CompoundTag compound) { + ItemStack stack = getStack(); + if (!stack.isEmpty()) { + compound.put("Item", stack.toTag(new CompoundTag())); + } + compound.putInt("State", getState().ordinal()); + } + + @Override + public boolean damage(DamageSource damageSource, float amount) { + if (isInvulnerableTo(damageSource) || !getStack().getItem().damage(damageSource)) { + return false; + } + + scheduleVelocityUpdate(); + + health -= amount; + if (health <= 0) { + remove(); + + ItemStack stack = getStack(); + + if (!(stack.getItem() instanceof Artifact) || ((Artifact)stack.getItem()).onArtifactDestroyed(this) != ActionResult.SUCCESS) { + dropStack(stack); + } + } + + return false; + } + + @Override + public boolean collides() { + return true; + } + + @Override + public Packet createSpawnPacket() { + return Channel.SERVER_SPAWN_PROJECTILE.toPacket(new MsgSpawnProjectile(this)); + } + + public enum State { + INITIALISING, + RUNNING, + SHUTTING_DOWN; + + static final State[] VALUES = values(); + + public float getVolume() { + return this == SHUTTING_DOWN ? 1 : 0.2F; + } + + public float getPitch() { + return this == INITIALISING ? 1 : this == RUNNING ? 2 : 0.5F; + } + + static State valueOf(int state) { + return state <= 0 || state >= VALUES.length ? INITIALISING : VALUES[state]; + } + } + + public interface Artifact { + void onArtifactTick(FloatingArtefactEntity entity); + + ActionResult onArtifactDestroyed(FloatingArtefactEntity entity); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ai/BreakHeartGoal.java b/src/main/java/com/minelittlepony/unicopia/entity/ai/BreakHeartGoal.java new file mode 100644 index 00000000..eda22f46 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/ai/BreakHeartGoal.java @@ -0,0 +1,92 @@ +package com.minelittlepony.unicopia.entity.ai; + +import java.util.Comparator; +import java.util.EnumSet; +import java.util.Optional; + +import javax.annotation.Nullable; + +import com.minelittlepony.unicopia.entity.FloatingArtefactEntity; +import com.minelittlepony.unicopia.item.UItems; +import com.minelittlepony.unicopia.util.VecHelper; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.mob.MobEntity; + +public class BreakHeartGoal extends Goal { + + private final MobEntity mob; + + @Nullable + private FloatingArtefactEntity target; + + private int cooldown; + + public BreakHeartGoal(MobEntity mob) { + this.mob = mob; + this.setControls(EnumSet.of(Goal.Control.MOVE, Goal.Control.LOOK)); + } + + @Override + public boolean canStart() { + Optional item = VecHelper.findInRange(mob, mob.world, mob.getPos(), 16, + e -> !e.removed && e instanceof FloatingArtefactEntity && ((FloatingArtefactEntity)e).getStack().getItem() == UItems.CRYSTAL_HEART) + .stream() + .map(e -> (FloatingArtefactEntity)e) + .sorted(Comparator.comparing((Entity e) -> mob.distanceTo(e))) + .findFirst(); + + if (item.isPresent()) { + this.target = item.get(); + return true; + } + + return false; + } + + @Override + public boolean shouldContinue() { + return (target == null || (target.isAlive() && mob.squaredDistanceTo(target) <= 225)) || canStart(); + } + + @Override + public void stop() { + target = null; + mob.getNavigation().stop(); + } + + @Override + public void tick() { + if (target == null || target.removed) { + target = null; + return; + } + + mob.getLookControl().lookAt(target, 30, 30); + + double reach = mob.getWidth() * 2 * mob.getWidth() * 2; + double distance = mob.squaredDistanceTo(target.getX(), target.getY(), target.getZ()); + + double speed = 0.9D; + + if (distance > reach && distance < 16) { + speed = 1.23; + } else if (distance < 225) { + speed = 0.6; + } + + mob.getNavigation().startMovingTo(target, speed); + + cooldown = Math.max(this.cooldown - 1, 0); + + if (target.getY() >= mob.getY() + 2) { + reach += 5; + } + + if (distance <= reach && cooldown <= 0) { + cooldown = 20; + mob.tryAttack(target); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItNeedItTargetGoal.java b/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItNeedItTargetGoal.java index 9490d2ae..c95d9d1e 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItNeedItTargetGoal.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItNeedItTargetGoal.java @@ -3,8 +3,7 @@ package com.minelittlepony.unicopia.entity.ai; import java.util.Comparator; import java.util.Optional; -import com.minelittlepony.unicopia.item.enchantment.UEnchantments; -import net.minecraft.enchantment.EnchantmentHelper; +import com.minelittlepony.unicopia.EquinePredicates; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.TargetPredicate; @@ -22,7 +21,7 @@ public class WantItNeedItTargetGoal extends Goal { public WantItNeedItTargetGoal(MobEntity mob) { this.predicate = new TargetPredicate() .setBaseMaxDistance(64) - .setPredicate(WantItNeedItTargetGoal::canTarget); + .setPredicate(EquinePredicates.HAS_WANT_IT_NEED_IT); this.mob = mob; } @@ -51,9 +50,4 @@ public class WantItNeedItTargetGoal extends Goal { LivingEntity target = mob.getTarget(); return target != null && mob.isTarget(target, TargetPredicate.DEFAULT); } - - static boolean canTarget(LivingEntity e) { - return EnchantmentHelper.getEquipmentLevel(UEnchantments.WANT_IT_NEED_IT, e) > 0; - } - } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItTakeItGoal.java b/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItTakeItGoal.java index 97a947a0..c61b0d57 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItTakeItGoal.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ai/WantItTakeItGoal.java @@ -7,6 +7,7 @@ import java.util.Optional; import javax.annotation.Nullable; import com.minelittlepony.unicopia.AwaitTickQueue; +import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; @@ -41,10 +42,10 @@ public class WantItTakeItGoal extends Goal { @Override public boolean canStart() { LivingEntity target = mob.getTarget(); - if (target == null || !WantItNeedItTargetGoal.canTarget(target)) { + if (target == null || !EquinePredicates.HAS_WANT_IT_NEED_IT.test(target)) { Optional item = VecHelper.findInRange(mob, mob.world, mob.getPos(), 16, - e -> !e.removed && e instanceof ItemEntity && EnchantmentHelper.getLevel(UEnchantments.WANT_IT_NEED_IT, ((ItemEntity)e).getStack()) > 0) + e -> !e.removed && mob.canSee(e) && e instanceof ItemEntity && EnchantmentHelper.getLevel(UEnchantments.WANT_IT_NEED_IT, ((ItemEntity)e).getStack()) > 0) .stream() .map(e -> (ItemEntity)e) .sorted(Comparator.comparing((Entity e) -> mob.distanceTo(e))) @@ -66,7 +67,7 @@ public class WantItTakeItGoal extends Goal { public boolean shouldContinue() { return (target == null || ( target.isAlive() - && WantItNeedItTargetGoal.canTarget(target) + && EquinePredicates.HAS_WANT_IT_NEED_IT.test(target) && mob.squaredDistanceTo(target) <= 225)) && (item == null || item.isAlive()) && (!mob.getNavigation().isIdle() || canStart()); diff --git a/src/main/java/com/minelittlepony/unicopia/item/CrystalHeartItem.java b/src/main/java/com/minelittlepony/unicopia/item/CrystalHeartItem.java new file mode 100644 index 00000000..c7b6ef52 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/item/CrystalHeartItem.java @@ -0,0 +1,186 @@ +package com.minelittlepony.unicopia.item; + +import java.util.ArrayList; +import java.util.List; + +import com.minelittlepony.unicopia.UEntities; +import com.minelittlepony.unicopia.UTags; +import com.minelittlepony.unicopia.entity.FloatingArtefactEntity; +import com.minelittlepony.unicopia.entity.FloatingArtefactEntity.State; +import com.minelittlepony.unicopia.particle.FollowingParticleEffect; +import com.minelittlepony.unicopia.particle.ParticleUtils; +import com.minelittlepony.unicopia.particle.UParticles; +import com.minelittlepony.unicopia.util.VecHelper; + +import net.minecraft.block.BlockState; +import net.minecraft.block.EndRodBlock; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.Saddleable; +import net.minecraft.entity.SpawnGroup; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.passive.TameableEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class CrystalHeartItem extends Item implements FloatingArtefactEntity.Artifact { + + public CrystalHeartItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + + World world = context.getWorld(); + BlockPos blockPos = new ItemPlacementContext(context).getBlockPos(); + + Box placementArea = UEntities.FLOATING_ARTEFACT.getDimensions().method_30757(Vec3d.ofBottomCenter(blockPos)); + + if (!world.isSpaceEmpty(null, placementArea, (entity) -> !(entity instanceof ItemEntity)) + || !world.getOtherEntities(null, placementArea).stream().noneMatch(e -> !(e instanceof ItemEntity))) { + return ActionResult.FAIL; + } + + if (world instanceof ServerWorld) { + + FloatingArtefactEntity entity = UEntities.FLOATING_ARTEFACT.create((ServerWorld)world, context.getStack().getTag(), null, context.getPlayer(), blockPos, SpawnReason.SPAWN_EGG, true, true); + + if (entity == null) { + return ActionResult.FAIL; + } + + entity.setStack(context.getStack().split(1)); + ((ServerWorld)world).spawnEntityAndPassengers(entity); + + entity.playSound(SoundEvents.BLOCK_BEACON_ACTIVATE, 0.75F, 0.8F); + } else { + context.getStack().decrement(1); + } + + return ActionResult.success(world.isClient); + } + + @Override + public void onArtifactTick(FloatingArtefactEntity entity) { + + if (entity.getState() == State.INITIALISING) { + if (findStructure(entity)) { + entity.setState(State.RUNNING); + } + } else { + if (!findStructure(entity)) { + entity.setState(State.INITIALISING); + } + + entity.addSpin(2); + + BlockPos pos = entity.getBlockPos(); + entity.world.addParticle(ParticleTypes.COMPOSTER, + pos.getX() + entity.world.getRandom().nextFloat(), + pos.getY() + entity.world.getRandom().nextFloat(), + pos.getZ() + entity.world.getRandom().nextFloat(), + 0, 0, 0); + + if (entity.world.getTime() % 80 == 0 && !entity.world.isClient) { + List inputs = new ArrayList<>(); + List outputs = new ArrayList<>(); + + VecHelper.findInRange(entity, entity.world, entity.getPos(), 20, EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.and(e -> !e.removed && (e instanceof PlayerEntity || e instanceof MobEntity))).forEach(e -> { + LivingEntity living = (LivingEntity)e; + + if (e instanceof PlayerEntity + || (living instanceof TameableEntity && ((TameableEntity)living).isTamed()) + || (living instanceof Saddleable && ((Saddleable)living).isSaddled())) { + if (living.getHealth() < living.getMaxHealth()) { + outputs.add(living); + } + } else if (e.getType().getSpawnGroup() == SpawnGroup.MONSTER) { + inputs.add(living); + } + }); + + int demand = outputs.size(); + int supply = inputs.size(); + + if (demand == 0 || supply == 0) { + return; + } + + float gives; + float takes; + + if (supply > demand) { + gives = supply / demand; + takes = 1; + } else if (demand > supply) { + takes = demand / supply; + gives = 1; + } else { + gives = 1; + takes = 1; + } + + inputs.forEach(input -> { + input.damage(DamageSource.MAGIC, takes); + ParticleUtils.spawnParticles(new FollowingParticleEffect(UParticles.HEALTH_DRAIN, entity, 0.2F), input, 1); + }); + outputs.forEach(output -> { + ParticleUtils.spawnParticles(new FollowingParticleEffect(UParticles.HEALTH_DRAIN, output, 0.2F), entity, 1); + output.heal(gives); + }); + } + } + + } + + @Override + public ActionResult onArtifactDestroyed(FloatingArtefactEntity entity) { + entity.playSound(SoundEvents.BLOCK_BEACON_DEACTIVATE, 0.75F, 1); + return ActionResult.PASS; + } + + private boolean findStructure(FloatingArtefactEntity entity) { + return findPyramid(entity, Direction.UP) && findPyramid(entity, Direction.DOWN); + } + + private boolean findPyramid(FloatingArtefactEntity entity, Direction direction) { + + BlockPos tip = entity.getBlockPos().offset(direction); + BlockState tipState = entity.world.getBlockState(tip); + if (!tipState.isIn(UTags.CRYSTAL_HEART_ORNAMENT) || (!tipState.contains(EndRodBlock.FACING)|| tipState.get(EndRodBlock.FACING) != direction.getOpposite())) { + return false; + } + + tip = tip.offset(direction); + if (!isDiamond(entity.world.getBlockState(tip))) { + return false; + } + tip = tip.offset(direction); + + final BlockPos center = tip; + + return BlockPos.streamOutwards(center, 1, 0, 1) + .filter(p -> p.getX() == center.getX() || p.getZ() == center.getZ()) + .map(entity.world::getBlockState) + .allMatch(this::isDiamond); + } + + private boolean isDiamond(BlockState state) { + return state.isIn(UTags.CRYSTAL_HEART_BASE); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/item/UItems.java b/src/main/java/com/minelittlepony/unicopia/item/UItems.java index 279088d1..f9674a0e 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/UItems.java +++ b/src/main/java/com/minelittlepony/unicopia/item/UItems.java @@ -55,6 +55,8 @@ public interface UItems { Item LIGHTNING_JAR = register("lightning_jar", new JarItem(new Item.Settings().group(ItemGroup.DECORATIONS).maxCount(1).fireproof(), false, false, true)); Item ZAP_APPLE_JAM_JAR = register("zap_apple_jam_jar", new JarItem(new Item.Settings().group(ItemGroup.DECORATIONS).maxCount(1).fireproof(), false, false, true)); + Item CRYSTAL_HEART = register("crystal_heart", new CrystalHeartItem(new Item.Settings().group(ItemGroup.DECORATIONS).maxCount(1))); + static T register(String name, T item) { ITEMS.add(item); if (item instanceof BlockItem) { diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java b/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java index 912820ae..aae869d1 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java @@ -5,12 +5,12 @@ import java.util.Optional; import com.minelittlepony.unicopia.Owned; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; -import net.minecraft.world.World; public class MsgSpawnProjectile extends EntitySpawnS2CPacket implements Channel.Packet { @@ -36,7 +36,7 @@ public class MsgSpawnProjectile extends EntitySpawnS2CPacket implements Channel. @SuppressWarnings("unchecked") @Override public void handle(PlayerEntity sender) { - World world = sender.world; + ClientWorld world = MinecraftClient.getInstance().world; Entity entity = getEntityTypeId().create(world); entity.updateTrackedPosition(getX(), getY(), getZ()); @@ -48,10 +48,10 @@ public class MsgSpawnProjectile extends EntitySpawnS2CPacket implements Channel. entity.setUuid(getUuid()); if (entity instanceof Owned) { - ((Owned) entity).setMaster(world.getEntityById(this.getEntityData())); + ((Owned) entity).setMaster(world.getEntityById(getEntityData())); } - ((ClientWorld)world).addEntity(getId(), entity); + world.addEntity(getId(), entity); } } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 3028af3f..190a733d 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -22,6 +22,8 @@ "item.unicopia.storm_cloud_jar": "Storm in a Jar", "item.unicopia.lightning_jar": "Lightning in a Jar", "item.unicopia.zap_apple_jam_jar": "Zap Apple Jam", + + "item.unicopia.crystal_heart": "Crystal Heart", "item.unicopia.music_disc_pet": "Music Disc", "item.unicopia.music_disc_pet.desc": "Danial Ingram - pet", diff --git a/src/main/resources/assets/unicopia/models/item/crystal_heart.json b/src/main/resources/assets/unicopia/models/item/crystal_heart.json new file mode 100644 index 00000000..a1c5d185 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/item/crystal_heart.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "unicopia:item/crystal_heart" + } +} diff --git a/src/main/resources/assets/unicopia/textures/item/crystal_heart.png b/src/main/resources/assets/unicopia/textures/item/crystal_heart.png new file mode 100644 index 00000000..b2da892a Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/item/crystal_heart.png differ diff --git a/src/main/resources/data/unicopia/tags/blocks/crystal_heart_base.json b/src/main/resources/data/unicopia/tags/blocks/crystal_heart_base.json new file mode 100644 index 00000000..059812e4 --- /dev/null +++ b/src/main/resources/data/unicopia/tags/blocks/crystal_heart_base.json @@ -0,0 +1,12 @@ +{ + "replace": false, + "values": [ + "minecraft:diamond_block", + "minecraft:quartz_block", + "minecraft:smooth_quartz", + "minecraft:chiseled_quartz_block", + "minecraft:quartz_pillar", + "minecraft:quartz_stairs", + "minecraft:smooth_quartz_stairs" + ] +} diff --git a/src/main/resources/data/unicopia/tags/blocks/crystal_heart_ornament.json b/src/main/resources/data/unicopia/tags/blocks/crystal_heart_ornament.json new file mode 100644 index 00000000..770f0fe5 --- /dev/null +++ b/src/main/resources/data/unicopia/tags/blocks/crystal_heart_ornament.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:end_rod" + ] +}