diff --git a/CHANGELOG.md b/CHANGELOG.md index 96d53fef7..339aa2a61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.7 + +- Fixed beekeeper villager trades +- Fixed infinite mauler exp feed (again) + ## 3.0.6 - Fixed strong potion of reaching duration diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/entity/MaulerEntity.java b/common/src/main/java/com/faboslav/friendsandfoes/common/entity/MaulerEntity.java index 21976cbda..124bc7a73 100644 --- a/common/src/main/java/com/faboslav/friendsandfoes/common/entity/MaulerEntity.java +++ b/common/src/main/java/com/faboslav/friendsandfoes/common/entity/MaulerEntity.java @@ -272,8 +272,11 @@ public ActionResult interactMob(PlayerEntity player, Hand hand) { !this.hasAngerTime() && ( ( - itemStack.hasEnchantments() - || itemInHand == Items.ENCHANTED_BOOK + !itemStack.isEmpty() + && ( + itemStack.hasEnchantments() + || itemInHand == Items.ENCHANTED_BOOK + ) ) ) ) { diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/init/FriendsAndFoesVillagerProfessions.java b/common/src/main/java/com/faboslav/friendsandfoes/common/init/FriendsAndFoesVillagerProfessions.java index 91fdfef70..a6855cb8f 100644 --- a/common/src/main/java/com/faboslav/friendsandfoes/common/init/FriendsAndFoesVillagerProfessions.java +++ b/common/src/main/java/com/faboslav/friendsandfoes/common/init/FriendsAndFoesVillagerProfessions.java @@ -5,7 +5,7 @@ import com.faboslav.friendsandfoes.common.init.registry.RegistryEntry; import com.faboslav.friendsandfoes.common.init.registry.ResourcefulRegistries; import com.faboslav.friendsandfoes.common.init.registry.ResourcefulRegistry; -import com.faboslav.friendsandfoes.common.util.TradeOffersUtil; +import com.faboslav.friendsandfoes.common.util.BasicTradeOffer; import com.google.common.collect.ImmutableSet; import net.minecraft.item.Items; import net.minecraft.registry.Registries; @@ -24,15 +24,17 @@ public final class FriendsAndFoesVillagerProfessions public static void registerVillagerTrades(RegisterVillagerTradesEvent event) { if (event.type() == BEEKEEPER.get()) { - event.register(1, new TradeOffersUtil.BuyForOneEmeraldFactory(FriendsAndFoesItems.BUTTERCUP.get(), 10, 16, 2)); - event.register(1, new TradeOffersUtil.BuyForOneEmeraldFactory(Items.DANDELION, 10, 16, 2)); - event.register(1, new TradeOffersUtil.BuyForOneEmeraldFactory(Items.SUNFLOWER, 10, 16, 2)); - event.register(2, new TradeOffersUtil.BuyForOneEmeraldFactory(Items.GLASS_BOTTLE, 9, 12, 10)); - event.register(2, new TradeOffersUtil.SellItemFactory(Items.HONEY_BOTTLE, 3, 1, 12, 5)); - event.register(3, new TradeOffersUtil.BuyForOneEmeraldFactory(Items.SHEARS, 1, 12, 20)); - event.register(3, new TradeOffersUtil.SellItemFactory(Items.HONEY_BLOCK, 10, 1, 12, 10)); - event.register(4, new TradeOffersUtil.SellItemFactory(Items.HONEYCOMB, 4, 1, 12, 15)); - event.register(5, new TradeOffersUtil.SellItemFactory(Items.HONEYCOMB_BLOCK, 12, 1, 12, 30)); + event.register(1, new BasicTradeOffer(FriendsAndFoesItems.BUTTERCUP.get(), Items.EMERALD, 10, 1, 16, 2, 0.05F)); + event.register(1, new BasicTradeOffer(Items.DANDELION, Items.EMERALD, 10, 1, 16, 2, 0.05F)); + event.register(1, new BasicTradeOffer(Items.SUNFLOWER, Items.EMERALD, 10, 1, 16, 2, 0.05F)); + event.register(2, new BasicTradeOffer(Items.GLASS_BOTTLE, Items.EMERALD, 9, 1, 12, 10, 0.05F)); + event.register(2, new BasicTradeOffer(Items.EMERALD, Items.HONEY_BOTTLE, 3, 1, 12, 5, 0.05F)); + event.register(3, new BasicTradeOffer(Items.SHEARS, Items.EMERALD, 1, 1, 12, 20, 0.05F)); + event.register(3, new BasicTradeOffer(Items.EMERALD, Items.HONEY_BLOCK, 10, 1, 12, 10, 0.05F)); + event.register(4, new BasicTradeOffer(Items.EMERALD, Items.HONEYCOMB, 4, 1, 12, 15, 0.05F)); + event.register(4, new BasicTradeOffer(Items.EMERALD, Items.HONEYCOMB_BLOCK, 12, 1, 12, 15, 0.05F)); + event.register(5, new BasicTradeOffer(Items.EMERALD, Items.BEEHIVE, 10, 1, 3, 30, 0.05F)); + event.register(5, new BasicTradeOffer(Items.EMERALD, Items.BEE_NEST, 20, 1, 3, 30, 0.05F)); } } diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/item/DispenserAddedSpawnEgg.java b/common/src/main/java/com/faboslav/friendsandfoes/common/item/DispenserAddedSpawnEgg.java index c4626bb98..2e6fed219 100644 --- a/common/src/main/java/com/faboslav/friendsandfoes/common/item/DispenserAddedSpawnEgg.java +++ b/common/src/main/java/com/faboslav/friendsandfoes/common/item/DispenserAddedSpawnEgg.java @@ -73,10 +73,6 @@ public FeatureSet getRequiredFeatures() { return getEntityType(null).getRequiredFeatures(); } - protected EntityType getDefaultType() { - return this.entityType.get(); - } - public static void onSetup(SetupEvent event) { var spawnEggMap = SpawnEggItemAccessor.variantsandventures$getSpawnEggs(); for (var entry : DispenserAddedSpawnEgg.SPAWN_EGGS) { diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/IllusionerEntityRendererMixin.java b/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/IllusionerEntityRendererMixin.java index 61bc717cc..551a512fc 100644 --- a/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/IllusionerEntityRendererMixin.java +++ b/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/IllusionerEntityRendererMixin.java @@ -10,6 +10,7 @@ import net.minecraft.client.render.entity.model.IllagerEntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.mob.IllusionerEntity; +import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -20,6 +21,8 @@ @Mixin({IllusionerEntityRenderer.class}) public abstract class IllusionerEntityRendererMixin extends IllagerEntityRenderer { + private final Identifier FRIENDSANDFOES_TEXTURE = FriendsAndFoes.makeID("textures/entity/illager/illusioner.png"); + protected IllusionerEntityRendererMixin( EntityRendererFactory.Context ctx, IllagerEntityModel model, @@ -61,4 +64,18 @@ protected void friendsandfoes_isVisible( callbackInfo.setReturnValue(super.isVisible(illusioner)); } } + + @Inject( + at = @At("HEAD"), + method = "getTexture(Lnet/minecraft/entity/mob/IllusionerEntity;)Lnet/minecraft/util/Identifier;", + cancellable = true + ) + protected void friendsandfoes_getTexture( + IllusionerEntity illusionerEntity, + CallbackInfoReturnable cir + ) { + if (FriendsAndFoes.getConfig().enableIllusioner) { + cir.setReturnValue(FRIENDSANDFOES_TEXTURE); + } + } } diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/PlayerEntityMixin.java b/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/PlayerEntityMixin.java index 3fe88b3e9..5de50ed75 100644 --- a/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/PlayerEntityMixin.java +++ b/common/src/main/java/com/faboslav/friendsandfoes/common/mixin/PlayerEntityMixin.java @@ -1,36 +1,27 @@ package com.faboslav.friendsandfoes.common.mixin; -import com.faboslav.friendsandfoes.common.entity.PlayerIllusionEntity; -import com.faboslav.friendsandfoes.common.init.FriendsAndFoesEntityTypes; import com.faboslav.friendsandfoes.common.init.FriendsAndFoesItems; -import com.faboslav.friendsandfoes.common.init.FriendsAndFoesSoundEvents; import com.faboslav.friendsandfoes.common.modcompat.ModChecker; import com.faboslav.friendsandfoes.common.modcompat.ModCompat; import com.faboslav.friendsandfoes.common.network.packet.TotemEffectPacket; import com.faboslav.friendsandfoes.common.tag.FriendsAndFoesTags; +import com.faboslav.friendsandfoes.common.util.TotemUtil; import net.minecraft.advancement.criterion.Criteria; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.ai.TargetPredicate; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageTypes; import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.effect.StatusEffects; -import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.particle.DefaultParticleType; -import net.minecraft.particle.ParticleTypes; import net.minecraft.registry.tag.FluidTags; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; import net.minecraft.stat.Stats; import net.minecraft.util.Hand; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; @@ -40,25 +31,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.Objects; -import java.util.function.Predicate; @Mixin(PlayerEntity.class) public abstract class PlayerEntityMixin extends LivingEntity { - private static final int MAX_ILLUSIONS_COUNT = 9; - private static final int ILLUSION_LIFETIME_TICKS = 600; - private static final int NEGATIVE_EFFECT_TICKS = 400; - private static final int POSITIVE_EFFECT_TICKS = 200; - private static final Predicate FREEZE_FILTER = (entity) -> { - return !(entity instanceof PlayerEntity) || !((PlayerEntity) entity).isCreative(); - }; - private static final TargetPredicate FREEZE_TARGET_PREDICATE = TargetPredicate.createNonAttackable().ignoreDistanceScalingFactor().ignoreVisibility().setPredicate(FREEZE_FILTER); - private static final TargetPredicate ATTACK_TARGET_PREDICATE = TargetPredicate.createNonAttackable().ignoreDistanceScalingFactor().ignoreVisibility(); - protected PlayerEntityMixin(EntityType entityType, World world) { super(entityType, world); } @@ -69,30 +47,6 @@ protected PlayerEntityMixin(EntityType entityType, World @Shadow public abstract Iterable getArmorItems(); - @Shadow - public double prevCapeX; - - @Shadow - public double prevCapeZ; - - @Shadow - public double prevCapeY; - - @Shadow - public double capeX; - - @Shadow - public double capeY; - - @Shadow - public double capeZ; - - @Shadow - public float prevStrideDistance; - - @Shadow - public float strideDistance; - @Inject( at = @At("TAIL"), method = "tick" @@ -152,10 +106,10 @@ public void friendsandfoes_tryUseTotems(DamageSource source, float amount, Callb totemItemStack.decrement(1); if (totemItem == FriendsAndFoesItems.TOTEM_OF_FREEZING.get()) { - this.friendsandfoes_freezeEntities(); - this.addStatusEffect(new StatusEffectInstance(StatusEffects.SPEED, POSITIVE_EFFECT_TICKS, 1)); + TotemUtil.freezeEntities(player); + this.addStatusEffect(new StatusEffectInstance(StatusEffects.SPEED, TotemUtil.POSITIVE_EFFECT_TICKS, 1)); } else if (totemItem == FriendsAndFoesItems.TOTEM_OF_ILLUSION.get()) { - this.friendsandfoes_createIllusions(); + TotemUtil.createIllusions(player); } } } @@ -195,167 +149,4 @@ private static ItemStack friendsandfoes_getTotemFromCustomEquipmentSlots(PlayerE private static boolean friendsandfoes_isTotem(ItemStack itemStack) { return itemStack.isIn(FriendsAndFoesTags.TOTEMS); } - - private void friendsandfoes_freezeEntities() { - List nearbyEntities = this.getWorld().getEntitiesByClass(LivingEntity.class, this.getBoundingBox().expand(9.0), (livingEntity) -> { - return FREEZE_TARGET_PREDICATE.test(this, livingEntity); - }); - - nearbyEntities.forEach(nearbyEntity -> { - nearbyEntity.setFrozenTicks(NEGATIVE_EFFECT_TICKS); - nearbyEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, NEGATIVE_EFFECT_TICKS, 1)); - }); - } - - private void friendsandfoes_createIllusions() { - this.playSound( - FriendsAndFoesSoundEvents.ENTITY_PLAYER_MIRROR_MOVE.get(), - this.getSoundVolume(), - this.getSoundPitch() - ); - - Vec3d illusionerPosition = this.getPos(); - float slice = 2.0F * (float) Math.PI / MAX_ILLUSIONS_COUNT; - int radius = 9; - - ArrayList createdPlayerIllusions = new ArrayList<>(); - - for (int point = 0; point < MAX_ILLUSIONS_COUNT; ++point) { - float angle = slice * point; - int x = (int) (illusionerPosition.getX() + radius * MathHelper.cos(angle)); - int y = (int) illusionerPosition.getY(); - int z = (int) (illusionerPosition.getZ() + radius * MathHelper.sin(angle)); - - PlayerIllusionEntity createdPlayerIllusion = this.friendsandfoes_createIllusion(x, y, z); - - if (createdPlayerIllusion != null) { - createdPlayerIllusions.add(createdPlayerIllusion); - } - } - - List nearbyEntities = this.getWorld().getEntitiesByClass(MobEntity.class, this.getBoundingBox().expand(18.0), (mobEntity) -> { - return ATTACK_TARGET_PREDICATE.test(this, mobEntity); - }); - - nearbyEntities.forEach(nearbyEntity -> { - if (nearbyEntity.getTarget() == this) { - if (!createdPlayerIllusions.isEmpty()) { - nearbyEntity.setAttacking(true); - nearbyEntity.setAttacker(createdPlayerIllusions.get(this.getRandom().nextInt(createdPlayerIllusions.size()))); - nearbyEntity.onAttacking(createdPlayerIllusions.get(this.getRandom().nextInt(createdPlayerIllusions.size()))); - } - - nearbyEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.BLINDNESS, NEGATIVE_EFFECT_TICKS, 1)); - } - }); - - if (!createdPlayerIllusions.isEmpty()) { - var illusionToReplace = createdPlayerIllusions.get(this.getRandom().nextInt(createdPlayerIllusions.size())); - boolean teleportResult = this.friendsandfoes_tryToTeleport(illusionToReplace.getBlockX(), illusionToReplace.getBlockY(), illusionToReplace.getBlockZ()); - - if (teleportResult) { - this.friendsandfoes_spawnCloudParticles(); - } - - var attacker = illusionToReplace.getAttacker(); - - if (attacker != null) { - illusionToReplace.setAttacking(null); - illusionToReplace.setAttacker(null); - illusionToReplace.onAttacking(null); - } - - illusionToReplace.discard(); - } - - this.addStatusEffect(new StatusEffectInstance(StatusEffects.INVISIBILITY, POSITIVE_EFFECT_TICKS)); - } - - @Nullable - private PlayerIllusionEntity friendsandfoes_createIllusion(int x, int y, int z) { - PlayerIllusionEntity playerIllusion = FriendsAndFoesEntityTypes.PLAYER_ILLUSION.get().create(this.getWorld()); - - if (playerIllusion == null) { - return null; - } - - playerIllusion.prevCapeX = this.prevCapeX; - playerIllusion.prevCapeY = this.prevCapeY; - playerIllusion.prevCapeZ = this.prevCapeZ; - playerIllusion.capeX = this.capeX; - playerIllusion.capeY = this.capeY; - playerIllusion.capeZ = this.capeZ; - playerIllusion.prevStrideDistance = this.prevStrideDistance; - playerIllusion.strideDistance = this.strideDistance; - - playerIllusion.equipStack(EquipmentSlot.MAINHAND, getMainHandStack().copy()); - playerIllusion.equipStack(EquipmentSlot.OFFHAND, getOffHandStack().copy()); - this.getArmorItems().forEach((item) -> playerIllusion.tryEquip(item.copy())); - - playerIllusion.setHealth(this.getMaxHealth()); - playerIllusion.copyPositionAndRotation(this); - float randomYaw = 360.F * this.getRandom().nextFloat(); - playerIllusion.prevYaw = randomYaw; - playerIllusion.setYaw(randomYaw); - playerIllusion.prevBodyYaw = randomYaw; - playerIllusion.setBodyYaw(randomYaw); - playerIllusion.prevHeadYaw = randomYaw; - playerIllusion.setHeadYaw(randomYaw); - playerIllusion.setPlayerUuid(this.getUuid()); - playerIllusion.setPlayer((PlayerEntity) (Object) this); - playerIllusion.setTicksUntilDespawn(ILLUSION_LIFETIME_TICKS); - - boolean teleportResult = playerIllusion.tryToTeleport(x, y, z); - - if (teleportResult) { - getWorld().spawnEntity(playerIllusion); - playerIllusion.spawnCloudParticles(); - } - - return playerIllusion; - } - - private boolean friendsandfoes_tryToTeleport(int x, int y, int z) { - y -= 8; - double bottomY = Math.max(y, getWorld().getBottomY()); - double topY = Math.min(bottomY + 16, ((ServerWorld) this.getWorld()).getLogicalHeight() - 1); - - for (int i = 0; i < 16; ++i) { - y = (int) MathHelper.clamp(y + 1, bottomY, topY); - boolean teleportResult = this.teleport(x, y, z, false); - - if (teleportResult) { - return true; - } - } - - return false; - } - - private void friendsandfoes_spawnCloudParticles() { - this.friendsandfoes_spawnParticles(ParticleTypes.CLOUD, 16); - } - - private void friendsandfoes_spawnParticles( - DefaultParticleType particleType, - int amount - ) { - if (this.getWorld().isClient()) { - return; - } - - for (int i = 0; i < amount; i++) { - ((ServerWorld) this.getEntityWorld()).spawnParticles( - particleType, - this.getParticleX(0.5D), - this.getRandomBodyY() + 0.5D, - this.getParticleZ(0.5D), - 1, - 0.0D, - 0.0D, - 0.0D, - 0.0D - ); - } - } } diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/util/BasicTradeOffer.java b/common/src/main/java/com/faboslav/friendsandfoes/common/util/BasicTradeOffer.java new file mode 100644 index 000000000..845f52eff --- /dev/null +++ b/common/src/main/java/com/faboslav/friendsandfoes/common/util/BasicTradeOffer.java @@ -0,0 +1,35 @@ +package com.faboslav.friendsandfoes.common.util; + +import net.minecraft.entity.Entity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.random.Random; +import net.minecraft.village.TradeOffer; +import net.minecraft.village.TradeOffers; + +public class BasicTradeOffer implements TradeOffers.Factory { + private final Item itemToTrade; + private final Item itemToReceive; + private final int amountToGive; + private final int amountToReceive; + protected final int maxUses; + protected final int experience; + protected final float multiplier; + + public BasicTradeOffer(Item itemToTrade, Item itemToReceive, int amountToGive, int amountToReceive, int maxUses, int experience, float multiplier) { + this.itemToTrade = itemToTrade; + this.itemToReceive = itemToReceive; + this.amountToGive = amountToGive; + this.amountToReceive = amountToReceive; + this.maxUses = maxUses; + this.experience = experience; + this.multiplier = multiplier; + } + + @Override + public TradeOffer create(Entity entity, Random random) { + ItemStack in = new ItemStack(this.itemToTrade, this.amountToGive); + ItemStack out = new ItemStack(this.itemToReceive, this.amountToReceive); + return new TradeOffer(in, out, this.maxUses, this.experience, this.multiplier); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/util/TotemUtil.java b/common/src/main/java/com/faboslav/friendsandfoes/common/util/TotemUtil.java index 12fca78da..6cf613a1a 100644 --- a/common/src/main/java/com/faboslav/friendsandfoes/common/util/TotemUtil.java +++ b/common/src/main/java/com/faboslav/friendsandfoes/common/util/TotemUtil.java @@ -1,15 +1,46 @@ package com.faboslav.friendsandfoes.common.util; +import com.faboslav.friendsandfoes.common.entity.PlayerIllusionEntity; +import com.faboslav.friendsandfoes.common.init.FriendsAndFoesEntityTypes; +import com.faboslav.friendsandfoes.common.init.FriendsAndFoesSoundEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.TargetPredicate; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.particle.DefaultParticleType; import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleType; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; public final class TotemUtil { + private static final int MAX_ILLUSIONS_COUNT = 9; + private static final int ILLUSION_LIFETIME_TICKS = 600; + private static final int NEGATIVE_EFFECT_TICKS = 400; + public static final int POSITIVE_EFFECT_TICKS = 200; + + private static final Predicate FREEZE_FILTER = (entity) -> { + return !(entity instanceof PlayerEntity) || !((PlayerEntity) entity).isCreative(); + }; + private static final TargetPredicate FREEZE_TARGET_PREDICATE = TargetPredicate.createNonAttackable().ignoreDistanceScalingFactor().ignoreVisibility().setPredicate(FREEZE_FILTER); + private static final TargetPredicate ATTACK_TARGET_PREDICATE = TargetPredicate.createNonAttackable().ignoreDistanceScalingFactor().ignoreVisibility(); + public static void playActivateAnimation(ItemStack itemStack, Entity entity, ParticleType particleType) { MinecraftClient minecraftClient = MinecraftClient.getInstance(); minecraftClient.particleManager.addEmitter(entity, (ParticleEffect) particleType, 30); @@ -24,4 +55,169 @@ public static void playActivateAnimation(ItemStack itemStack, Entity entity, Par minecraftClient.gameRenderer.showFloatingItem(itemStack); } } + + + public static void freezeEntities(PlayerEntity player) { + List nearbyEntities = player.getWorld().getEntitiesByClass(LivingEntity.class, player.getBoundingBox().expand(9.0), (livingEntity) -> { + return FREEZE_TARGET_PREDICATE.test(player, livingEntity); + }); + + nearbyEntities.forEach(nearbyEntity -> { + nearbyEntity.setFrozenTicks(NEGATIVE_EFFECT_TICKS); + nearbyEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, NEGATIVE_EFFECT_TICKS, 1)); + }); + } + + public static void createIllusions(PlayerEntity player) { + player.playSound( + FriendsAndFoesSoundEvents.ENTITY_PLAYER_MIRROR_MOVE.get(), + 1.0F, + player.getSoundPitch() + ); + + Vec3d illusionerPosition = player.getPos(); + float slice = 2.0F * (float) Math.PI / MAX_ILLUSIONS_COUNT; + int radius = 9; + + ArrayList createdPlayerIllusions = new ArrayList<>(); + + for (int point = 0; point < MAX_ILLUSIONS_COUNT; ++point) { + float angle = slice * point; + int x = (int) (illusionerPosition.getX() + radius * MathHelper.cos(angle)); + int y = (int) illusionerPosition.getY(); + int z = (int) (illusionerPosition.getZ() + radius * MathHelper.sin(angle)); + + PlayerIllusionEntity createdPlayerIllusion = createIllusion(player, x, y, z); + + if (createdPlayerIllusion != null) { + createdPlayerIllusions.add(createdPlayerIllusion); + } + } + + List nearbyEntities = player.getWorld().getEntitiesByClass(MobEntity.class, player.getBoundingBox().expand(18.0), (mobEntity) -> { + return ATTACK_TARGET_PREDICATE.test(player, mobEntity); + }); + + nearbyEntities.forEach(nearbyEntity -> { + if (nearbyEntity.getTarget() == player) { + if (!createdPlayerIllusions.isEmpty()) { + nearbyEntity.setAttacking(true); + nearbyEntity.setAttacker(createdPlayerIllusions.get(player.getRandom().nextInt(createdPlayerIllusions.size()))); + nearbyEntity.onAttacking(createdPlayerIllusions.get(player.getRandom().nextInt(createdPlayerIllusions.size()))); + } + + nearbyEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.BLINDNESS, NEGATIVE_EFFECT_TICKS, 1)); + } + }); + + if (!createdPlayerIllusions.isEmpty()) { + var illusionToReplace = createdPlayerIllusions.get(player.getRandom().nextInt(createdPlayerIllusions.size())); + boolean teleportResult = tryToTeleport(player, illusionToReplace.getBlockX(), illusionToReplace.getBlockY(), illusionToReplace.getBlockZ()); + + if (teleportResult) { + spawnCloudParticles(player); + } + + var attacker = illusionToReplace.getAttacker(); + + if (attacker != null) { + illusionToReplace.setAttacking(null); + illusionToReplace.setAttacker(null); + illusionToReplace.onAttacking(null); + } + + illusionToReplace.discard(); + } + + player.addStatusEffect(new StatusEffectInstance(StatusEffects.INVISIBILITY, POSITIVE_EFFECT_TICKS)); + } + + @Nullable + private static PlayerIllusionEntity createIllusion(PlayerEntity player, int x, int y, int z) { + PlayerIllusionEntity playerIllusion = FriendsAndFoesEntityTypes.PLAYER_ILLUSION.get().create(player.getWorld()); + + if (playerIllusion == null) { + return null; + } + + playerIllusion.prevCapeX = player.prevCapeX; + playerIllusion.prevCapeY = player.prevCapeY; + playerIllusion.prevCapeZ = player.prevCapeZ; + playerIllusion.capeX = player.capeX; + playerIllusion.capeY = player.capeY; + playerIllusion.capeZ = player.capeZ; + playerIllusion.prevStrideDistance = player.prevStrideDistance; + playerIllusion.strideDistance = player.strideDistance; + + playerIllusion.equipStack(EquipmentSlot.MAINHAND, player.getMainHandStack().copy()); + playerIllusion.equipStack(EquipmentSlot.OFFHAND, player.getOffHandStack().copy()); + player.getArmorItems().forEach((item) -> playerIllusion.tryEquip(item.copy())); + + playerIllusion.setHealth(player.getMaxHealth()); + playerIllusion.copyPositionAndRotation(player); + float randomYaw = 360.F * player.getRandom().nextFloat(); + playerIllusion.prevYaw = randomYaw; + playerIllusion.setYaw(randomYaw); + playerIllusion.prevBodyYaw = randomYaw; + playerIllusion.setBodyYaw(randomYaw); + playerIllusion.prevHeadYaw = randomYaw; + playerIllusion.setHeadYaw(randomYaw); + playerIllusion.setPlayerUuid(player.getUuid()); + playerIllusion.setPlayer(player); + playerIllusion.setTicksUntilDespawn(ILLUSION_LIFETIME_TICKS); + + boolean teleportResult = playerIllusion.tryToTeleport(x, y, z); + + if (teleportResult) { + player.getWorld().spawnEntity(playerIllusion); + playerIllusion.spawnCloudParticles(); + } + + return playerIllusion; + } + + private static boolean tryToTeleport(PlayerEntity player, int x, int y, int z) { + y -= 8; + double bottomY = Math.max(y, player.getWorld().getBottomY()); + double topY = Math.min(bottomY + 16, ((ServerWorld) player.getWorld()).getLogicalHeight() - 1); + + for (int i = 0; i < 16; ++i) { + y = (int) MathHelper.clamp(y + 1, bottomY, topY); + boolean teleportResult = player.teleport(x, y, z, false); + + if (teleportResult) { + return true; + } + } + + return false; + } + + private static void spawnCloudParticles(PlayerEntity player) { + spawnParticles(player, ParticleTypes.CLOUD, 16); + } + + private static void spawnParticles( + PlayerEntity player, + DefaultParticleType particleType, + int amount + ) { + if (player.getWorld().isClient()) { + return; + } + + for (int i = 0; i < amount; i++) { + ((ServerWorld) player.getWorld()).spawnParticles( + particleType, + player.getParticleX(0.5D), + player.getRandomBodyY() + 0.5D, + player.getParticleZ(0.5D), + 1, + 0.0D, + 0.0D, + 0.0D, + 0.0D + ); + } + } } diff --git a/common/src/main/java/com/faboslav/friendsandfoes/common/util/TradeOffersUtil.java b/common/src/main/java/com/faboslav/friendsandfoes/common/util/TradeOffersUtil.java deleted file mode 100644 index 69ca406bb..000000000 --- a/common/src/main/java/com/faboslav/friendsandfoes/common/util/TradeOffersUtil.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.faboslav.friendsandfoes.common.util; - -import net.minecraft.entity.Entity; -import net.minecraft.item.Item; -import net.minecraft.item.ItemConvertible; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.util.math.random.Random; -import net.minecraft.village.TradeOffer; -import net.minecraft.village.TradeOffers; - -public final class TradeOffersUtil -{ - public static class SellItemFactory implements TradeOffers.Factory - { - private final ItemStack sell; - private final int price; - private final int count; - private final int maxUses; - private final int experience; - private final float multiplier; - - public SellItemFactory(Item item, int price, int count, int maxUses, int experience) { - this(new ItemStack(item), price, count, maxUses, experience); - } - - public SellItemFactory(ItemStack stack, int price, int count, int maxUses, int experience) { - this(stack, price, count, maxUses, experience, 0.05F); - } - - public SellItemFactory(ItemStack stack, int price, int count, int maxUses, int experience, float multiplier) { - this.sell = stack; - this.price = price; - this.count = count; - this.maxUses = maxUses; - this.experience = experience; - this.multiplier = multiplier; - } - - public TradeOffer create(Entity entity, Random random) { - return new TradeOffer(new ItemStack(Items.EMERALD, this.price), new ItemStack(this.sell.getItem(), this.count), this.maxUses, this.experience, this.multiplier); - } - } - - public static class BuyForOneEmeraldFactory implements TradeOffers.Factory - { - private final Item buy; - private final int price; - private final int maxUses; - private final int experience; - private final float multiplier; - - public BuyForOneEmeraldFactory(ItemConvertible item, int price, int maxUses, int experience) { - this.buy = item.asItem(); - this.price = price; - this.maxUses = maxUses; - this.experience = experience; - this.multiplier = 0.05F; - } - - public TradeOffer create(Entity entity, Random random) { - ItemStack itemStack = new ItemStack(this.buy, this.price); - return new TradeOffer(itemStack, new ItemStack(Items.EMERALD), this.maxUses, this.experience, this.multiplier); - } - } - - private TradeOffersUtil() { - } -} diff --git a/common/src/main/resources/assets/minecraft/textures/entity/illager/illusioner.png b/common/src/main/resources/assets/friendsandfoes/textures/entity/illager/illusioner.png similarity index 100% rename from common/src/main/resources/assets/minecraft/textures/entity/illager/illusioner.png rename to common/src/main/resources/assets/friendsandfoes/textures/entity/illager/illusioner.png diff --git a/gradle.properties b/gradle.properties index 2cf43e30b..f580a9661 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.caching=false mod_java_version=17 mod_name=Friends & Foes mod_id=friendsandfoes -mod_version=3.0.6 +mod_version=3.0.7 mod_author=Faboslav mod_description=Adds outvoted and forgotten mobs from the mob vote, expanding on their original concepts and introducing new vanilla-like features. maven_group=com.faboslav.friendsandfoes