diff --git a/src/generated/resources/.cache/3640dae69333058667d0ac745c9dd53ff5162a43 b/src/generated/resources/.cache/3640dae69333058667d0ac745c9dd53ff5162a43 index 4b45d6d..ad457b0 100644 --- a/src/generated/resources/.cache/3640dae69333058667d0ac745c9dd53ff5162a43 +++ b/src/generated/resources/.cache/3640dae69333058667d0ac745c9dd53ff5162a43 @@ -1,4 +1,5 @@ -// 1.21 2024-07-26T11:42:10.7600866 Item Models: celebrations +// 1.21 2024-08-31T20:50:35.7179308 Item Models: celebrations +8efe7fdaedc19c87bd5600b7ed21cb12f5d57eff assets/celebrations/models/item/balloon.json 4a0465436abc24d1696617288f0597d06b41a9d7 assets/celebrations/models/item/chinese_styled_bamboo_lantern.json 456fb978cf2a416a9cfad9e18fbde0ee711352c4 assets/celebrations/models/item/chinese_styled_paper_lantern.json d716b049310b4b1e98454511dfd779b66711ea88 assets/celebrations/models/item/chinese_styled_red_lantern.json diff --git a/src/generated/resources/.cache/b53530ffe8f641d659e694e00e12c85c2c363439 b/src/generated/resources/.cache/b53530ffe8f641d659e694e00e12c85c2c363439 index 0d96eb2..fee16c9 100644 --- a/src/generated/resources/.cache/b53530ffe8f641d659e694e00e12c85c2c363439 +++ b/src/generated/resources/.cache/b53530ffe8f641d659e694e00e12c85c2c363439 @@ -1,2 +1,2 @@ -// 1.21 2024-07-28T12:41:42.2679323 Languages: zh_cn for mod: celebrations -41a69f30d929b996cf86dac65b1e01f6796b4830 assets/celebrations/lang/zh_cn.json +// 1.21 2024-08-31T20:50:35.7179308 Languages: zh_cn for mod: celebrations +3ccff4f74ebb1700a8e119a2e5bbd87eaa20a7fb assets/celebrations/lang/zh_cn.json diff --git a/src/generated/resources/.cache/fb495bcf374953a513aaf0fcc5c297e346a4f26b b/src/generated/resources/.cache/fb495bcf374953a513aaf0fcc5c297e346a4f26b index 6c5af64..dab2b98 100644 --- a/src/generated/resources/.cache/fb495bcf374953a513aaf0fcc5c297e346a4f26b +++ b/src/generated/resources/.cache/fb495bcf374953a513aaf0fcc5c297e346a4f26b @@ -1,2 +1,2 @@ -// 1.21 2024-07-28T12:41:42.2689501 Languages: en_us for mod: celebrations -def096f772a649bde461c0ea664485395b5c3d3e assets/celebrations/lang/en_us.json +// 1.21 2024-08-31T20:50:35.7179308 Languages: en_us for mod: celebrations +43f1aa404e8ae53e01195d659f596b2e9f695869 assets/celebrations/lang/en_us.json diff --git a/src/generated/resources/assets/celebrations/lang/en_us.json b/src/generated/resources/assets/celebrations/lang/en_us.json index 0dd4886..b343514 100644 --- a/src/generated/resources/assets/celebrations/lang/en_us.json +++ b/src/generated/resources/assets/celebrations/lang/en_us.json @@ -20,7 +20,9 @@ "celebrations.configuration.lanternGiveEnemyHarmfulEffect": "Lanterns Give Enemies Harmful Effects", "celebrations.configuration.lanternGiveNonEnemyBeneficialEffect": "Lanterns Give Non-enemy Mobs Beneficial Effects", "celebrations.configuration.lanternGiveNonEnemyHarmfulEffect": "Lanterns Give Non-enemy Mobs Harmful Effects", + "entity.celebrations.balloon": "Balloon", "fml.menu.mods.info.description.celebrations": "A mod about celebrating!", + "item.celebrations.balloon": "Balloon", "item.celebrations.gold_powder": "Gold Powder", "item.celebrations.party_hat": "Party Hat", "item.celebrations.red_paper": "Red Paper", diff --git a/src/generated/resources/assets/celebrations/lang/zh_cn.json b/src/generated/resources/assets/celebrations/lang/zh_cn.json index f48a3ca..6742363 100644 --- a/src/generated/resources/assets/celebrations/lang/zh_cn.json +++ b/src/generated/resources/assets/celebrations/lang/zh_cn.json @@ -20,7 +20,9 @@ "celebrations.configuration.lanternGiveEnemyHarmfulEffect": "灯笼给予敌对生物负面效果", "celebrations.configuration.lanternGiveNonEnemyBeneficialEffect": "灯笼给予非敌对生物增益效果", "celebrations.configuration.lanternGiveNonEnemyHarmfulEffect": "灯笼给予非敌对生物负面效果", + "entity.celebrations.balloon": "气球", "fml.menu.mods.info.description.celebrations": "一个关于庆祝的模组!", + "item.celebrations.balloon": "气球", "item.celebrations.gold_powder": "金粉", "item.celebrations.party_hat": "派对帽", "item.celebrations.red_paper": "红纸", diff --git a/src/generated/resources/assets/celebrations/models/item/balloon.json b/src/generated/resources/assets/celebrations/models/item/balloon.json new file mode 100644 index 0000000..a91f49e --- /dev/null +++ b/src/generated/resources/assets/celebrations/models/item/balloon.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "celebrations:item/balloon_leash", + "layer1": "celebrations:item/balloon" + } +} \ No newline at end of file diff --git a/src/main/java/team/leomc/celebrations/Celebrations.java b/src/main/java/team/leomc/celebrations/Celebrations.java index 362e3fe..ce56313 100644 --- a/src/main/java/team/leomc/celebrations/Celebrations.java +++ b/src/main/java/team/leomc/celebrations/Celebrations.java @@ -20,6 +20,7 @@ public Celebrations(IEventBus modBus, ModContainer container) { CDataComponents.DATA_COMPONENTS.register(modBus); CItems.ITEMS.register(modBus); CCreativeModeTabs.TABS.register(modBus); + CEntities.ENTITY_TYPES.register(modBus); CRecipeSerializers.RECIPE_SERIALIZERS.register(modBus); } diff --git a/src/main/java/team/leomc/celebrations/block/CLanternBlock.java b/src/main/java/team/leomc/celebrations/block/CLanternBlock.java index 596b1a1..a395339 100644 --- a/src/main/java/team/leomc/celebrations/block/CLanternBlock.java +++ b/src/main/java/team/leomc/celebrations/block/CLanternBlock.java @@ -152,12 +152,12 @@ protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Lev } if (stack.has(DataComponents.POTION_CONTENTS) && level.getBlockEntity(pos) instanceof LanternBlockEntity lanternBlockEntity) { PotionContents potions = stack.get(DataComponents.POTION_CONTENTS); - if (potions != null && potions.potion().isPresent()) { + if (potions != null && potions.potion().isPresent() && stack.is(Items.POTION)) { Potion potion = potions.potion().get().value(); - if (potion.equals(Potions.WATER)) { + if (potion == Potions.WATER.value()) { lanternBlockEntity.clearEffects(); level.setBlockAndUpdate(pos, state.setValue(LIT, false)); - if (!player.getAbilities().instabuild) { + if (!player.hasInfiniteMaterials()) { player.setItemInHand(hand, Items.GLASS_BOTTLE.getDefaultInstance()); } return ItemInteractionResult.sidedSuccess(level.isClientSide); @@ -166,7 +166,7 @@ protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Lev for (MobEffectInstance instance : potions.getAllEffects()) { if (!instance.getEffect().value().isInstantenous()) { lanternBlockEntity.addEffect(new MobEffectInstance(instance)); - if (!player.getAbilities().instabuild) { + if (!player.hasInfiniteMaterials()) { player.setItemInHand(hand, Items.GLASS_BOTTLE.getDefaultInstance()); } succeed = true; diff --git a/src/main/java/team/leomc/celebrations/client/event/CClientEvents.java b/src/main/java/team/leomc/celebrations/client/event/CClientEvents.java new file mode 100644 index 0000000..4666974 --- /dev/null +++ b/src/main/java/team/leomc/celebrations/client/event/CClientEvents.java @@ -0,0 +1,121 @@ +package team.leomc.celebrations.client.event; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderDispatcher; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import org.joml.Matrix4f; +import team.leomc.celebrations.Celebrations; +import team.leomc.celebrations.entity.Balloon; + +@OnlyIn(Dist.CLIENT) +@EventBusSubscriber(modid = Celebrations.ID, value = Dist.CLIENT) +public class CClientEvents { + public static final int FULL_BRIGHT = 0xf000f0; + public static final Int2ObjectArrayMap PLAYER_LEFT_HAND_POS = new Int2ObjectArrayMap<>(); + public static final Int2ObjectArrayMap PLAYER_RIGHT_HAND_POS = new Int2ObjectArrayMap<>(); + public static final Int2IntArrayMap BALLOON_LIGHT = new Int2IntArrayMap(); + + @SubscribeEvent + private static void onRenderLevelStage(RenderLevelStageEvent event) { + ClientLevel level = Minecraft.getInstance().level; + EntityRenderDispatcher entityRenderDispatcher = Minecraft.getInstance().getEntityRenderDispatcher(); + PoseStack stack = event.getPoseStack(); + MultiBufferSource buffer = event.getLevelRenderer().renderBuffers.bufferSource(); + Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); + Vec3 cameraPos = camera.getPosition(); + + if (event.getStage() == RenderLevelStageEvent.Stage.AFTER_SKY) { + PLAYER_LEFT_HAND_POS.clear(); + PLAYER_RIGHT_HAND_POS.clear(); + BALLOON_LIGHT.clear(); + } + + if (level != null && event.getStage() == RenderLevelStageEvent.Stage.AFTER_ENTITIES) { + stack.pushPose(); + stack.translate(-cameraPos.x(), -cameraPos.y(), -cameraPos.z()); + float partialTicks = Minecraft.getInstance().getTimer().getGameTimeDeltaPartialTick(Minecraft.getInstance().level != null && Minecraft.getInstance().level.tickRateManager().runsNormally()); + for (Entity entity : level.entitiesForRendering()) { + if (entity instanceof Balloon balloon) { + LivingEntity owner = balloon.getOwner(); + if (owner instanceof Player player) { + HumanoidArm arm = balloon.isMainHand() ? player.getMainArm() : player.getMainArm().getOpposite(); + Vec3 handPos = arm == HumanoidArm.RIGHT ? PLAYER_RIGHT_HAND_POS.get(player.getId()) : PLAYER_LEFT_HAND_POS.get(player.getId()); + if (entityRenderDispatcher.options.getCameraType().isFirstPerson() && owner == Minecraft.getInstance().player) { + int hand = owner.getMainArm() == HumanoidArm.RIGHT ? 1 : -1; + if (!balloon.isMainHand()) { + hand = -hand; + } + Vec3 vec3 = entityRenderDispatcher.camera.getNearPlane().getPointOnPlane(hand * 1.2f, -1.2f).scale(960.0 / (double) entityRenderDispatcher.options.fov().get()); + handPos = owner.getEyePosition(partialTicks).add(vec3); + } + if (handPos != null) { + stack.pushPose(); + Vec3 entityPos = balloon.getPosition(partialTicks); + stack.translate(entityPos.x, entityPos.y, entityPos.z); + renderLeash(handPos, entityPos, BALLOON_LIGHT.getOrDefault(balloon.getId(), FULL_BRIGHT), stack, buffer); + stack.popPose(); + } + } + } + } + stack.popPose(); + } + } + + private static void renderLeash(Vec3 from, Vec3 to, int light, PoseStack poseStack, MultiBufferSource bufferSource) { + poseStack.pushPose(); + double d3 = to.x; + double d4 = to.y; + double d5 = to.z; + float f = (float) (from.x - d3); + float f1 = (float) (from.y - d4); + float f2 = (float) (from.z - d5); + VertexConsumer vertexconsumer = bufferSource.getBuffer(RenderType.leash()); + Matrix4f matrix4f = poseStack.last().pose(); + float f4 = Mth.invSqrt(f * f + f2 * f2) * 0.025F / 2.0F; + float f5 = f2 * f4; + float f6 = f * f4; + + int j1; + for (j1 = 0; j1 <= 24; ++j1) { + addVertexPair(vertexconsumer, matrix4f, f, f1, f2, light, 0.025F, 0.025F, f5, f6, j1, false); + } + + for (j1 = 24; j1 >= 0; --j1) { + addVertexPair(vertexconsumer, matrix4f, f, f1, f2, light, 0.025F, 0.0F, f5, f6, j1, true); + } + + poseStack.popPose(); + } + + private static void addVertexPair(VertexConsumer buffer, Matrix4f pose, float startX, float startY, float startZ, int light, float yOffset, float dy, float dx, float dz, int index, boolean reverse) { + float f = (float) index / 24.0F; + float f1 = index % 2 == (reverse ? 1 : 0) ? 0.7F : 1.0F; + float f2 = 0.5F * f1; + float f3 = 0.4F * f1; + float f4 = 0.3F * f1; + float f5 = startX * f; + float f6 = startY > 0.0F ? startY * f * f : startY - startY * (1.0F - f) * (1.0F - f); + float f7 = startZ * f; + buffer.addVertex(pose, f5 - dx, f6 + dy, f7 + dz).setColor(f2, f3, f4, 1.0F).setLight(light); + buffer.addVertex(pose, f5 + dx, f6 + yOffset - dy, f7 - dz).setColor(f2, f3, f4, 1.0F).setLight(light); + } +} diff --git a/src/main/java/team/leomc/celebrations/client/event/CClientSetupEvents.java b/src/main/java/team/leomc/celebrations/client/event/CClientSetupEvents.java index 489e591..74b7446 100644 --- a/src/main/java/team/leomc/celebrations/client/event/CClientSetupEvents.java +++ b/src/main/java/team/leomc/celebrations/client/event/CClientSetupEvents.java @@ -1,6 +1,5 @@ package team.leomc.celebrations.client.event; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; import net.minecraft.client.renderer.item.ItemProperties; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; @@ -10,12 +9,16 @@ import net.neoforged.neoforge.client.event.EntityRenderersEvent; import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; import team.leomc.celebrations.Celebrations; -import team.leomc.celebrations.client.renderer.CoupletRenderer; -import team.leomc.celebrations.client.renderer.FuStickerRenderer; -import team.leomc.celebrations.client.renderer.HorizontalScrollRenderer; +import team.leomc.celebrations.client.model.entity.BalloonModel; +import team.leomc.celebrations.client.renderer.block.CoupletRenderer; +import team.leomc.celebrations.client.renderer.block.FuStickerRenderer; +import team.leomc.celebrations.client.renderer.block.HorizontalScrollRenderer; +import team.leomc.celebrations.client.renderer.entity.BalloonRenderer; +import team.leomc.celebrations.item.component.BalloonData; import team.leomc.celebrations.item.component.PartyHat; import team.leomc.celebrations.registry.CBlockEntities; import team.leomc.celebrations.registry.CDataComponents; +import team.leomc.celebrations.registry.CEntities; import team.leomc.celebrations.registry.CItems; @OnlyIn(Dist.CLIENT) @@ -23,10 +26,6 @@ public class CClientSetupEvents { @SubscribeEvent private static void onClientSetup(FMLClientSetupEvent event) { - BlockEntityRenderers.register(CBlockEntities.COUPLET.get(), CoupletRenderer::new); - BlockEntityRenderers.register(CBlockEntities.HORIZONTAL_SCROLL.get(), HorizontalScrollRenderer::new); - BlockEntityRenderers.register(CBlockEntities.FU_STICKER.get(), FuStickerRenderer::new); - ItemProperties.register(CItems.PARTY_HAT.get(), Celebrations.id("party_hat_type"), (itemStack, clientLevel, livingEntity, i) -> { PartyHat partyHat = itemStack.get(CDataComponents.PART_HAT.get()); if (partyHat != null) { @@ -40,16 +39,29 @@ private static void onClientSetup(FMLClientSetupEvent event) { }); } + @SubscribeEvent + private static void onRegisterEntityRenderers(EntityRenderersEvent.RegisterRenderers event) { + event.registerEntityRenderer(CEntities.BALLOON.get(), BalloonRenderer::new); + event.registerBlockEntityRenderer(CBlockEntities.COUPLET.get(), CoupletRenderer::new); + event.registerBlockEntityRenderer(CBlockEntities.HORIZONTAL_SCROLL.get(), HorizontalScrollRenderer::new); + event.registerBlockEntityRenderer(CBlockEntities.FU_STICKER.get(), FuStickerRenderer::new); + } + @SubscribeEvent private static void onRegisterItemColorHandlers(RegisterColorHandlersEvent.Item event) { event.register((itemStack, i) -> { PartyHat hat = itemStack.get(CDataComponents.PART_HAT.get()); return i == 1 ? (hat == null ? -1 : hat.color().getTextureDiffuseColor()) : -1; }, CItems.PARTY_HAT.get()); + event.register((itemStack, i) -> { + BalloonData data = itemStack.get(CDataComponents.BALLOON_DATA.get()); + return i == 1 ? (data == null ? -1 : data.color().getTextureDiffuseColor()) : -1; + }, CItems.BALLOON.get()); } @SubscribeEvent public static void onRegisterLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) { + event.registerLayerDefinition(BalloonModel.LAYER_LOCATION, BalloonModel::createBodyLayer); event.registerLayerDefinition(CoupletRenderer.LAYER_LOCATION, CoupletRenderer::createCoupletLayer); event.registerLayerDefinition(HorizontalScrollRenderer.LAYER_LOCATION, HorizontalScrollRenderer::createScrollLayer); event.registerLayerDefinition(FuStickerRenderer.LAYER_LOCATION, FuStickerRenderer::createStickerLayer); diff --git a/src/main/java/team/leomc/celebrations/client/gui/screen/CoupletEditScreen.java b/src/main/java/team/leomc/celebrations/client/gui/screen/CoupletEditScreen.java index 687735c..a1adc8a 100644 --- a/src/main/java/team/leomc/celebrations/client/gui/screen/CoupletEditScreen.java +++ b/src/main/java/team/leomc/celebrations/client/gui/screen/CoupletEditScreen.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable; import team.leomc.celebrations.Celebrations; import team.leomc.celebrations.block.entity.CoupletBlockEntity; -import team.leomc.celebrations.client.renderer.CoupletRenderer; +import team.leomc.celebrations.client.renderer.block.CoupletRenderer; import team.leomc.celebrations.network.UpdateSignLikeTextPayload; import team.leomc.celebrations.util.SignLikeText; diff --git a/src/main/java/team/leomc/celebrations/client/gui/screen/HorizontalScrollEditScreen.java b/src/main/java/team/leomc/celebrations/client/gui/screen/HorizontalScrollEditScreen.java index 88f0337..08a77bf 100644 --- a/src/main/java/team/leomc/celebrations/client/gui/screen/HorizontalScrollEditScreen.java +++ b/src/main/java/team/leomc/celebrations/client/gui/screen/HorizontalScrollEditScreen.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable; import team.leomc.celebrations.Celebrations; import team.leomc.celebrations.block.entity.HorizontalScrollBlockEntity; -import team.leomc.celebrations.client.renderer.HorizontalScrollRenderer; +import team.leomc.celebrations.client.renderer.block.HorizontalScrollRenderer; import team.leomc.celebrations.network.UpdateSignLikeTextPayload; import team.leomc.celebrations.util.SignLikeText; diff --git a/src/main/java/team/leomc/celebrations/client/model/entity/BalloonModel.java b/src/main/java/team/leomc/celebrations/client/model/entity/BalloonModel.java new file mode 100644 index 0000000..b4485ec --- /dev/null +++ b/src/main/java/team/leomc/celebrations/client/model/entity/BalloonModel.java @@ -0,0 +1,43 @@ +package team.leomc.celebrations.client.model.entity; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.model.EntityModel; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.*; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import team.leomc.celebrations.Celebrations; +import team.leomc.celebrations.entity.Balloon; + +@OnlyIn(Dist.CLIENT) +public class BalloonModel extends EntityModel { + public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(Celebrations.id("balloon"), "main"); + private final ModelPart root; + + public BalloonModel(ModelPart root) { + this.root = root.getChild("root"); + } + + public static LayerDefinition createBodyLayer() { + MeshDefinition meshdefinition = new MeshDefinition(); + PartDefinition partdefinition = meshdefinition.getRoot(); + + partdefinition.addOrReplaceChild("root", CubeListBuilder.create().texOffs(0, 0).addBox(-5.0F, -12.0F, -5.0F, 10.0F, 10.0F, 10.0F, new CubeDeformation(0.0F)) + .texOffs(0, 20).addBox(-3.0F, -2.0F, -3.0F, 6.0F, 2.0F, 6.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 24.0F, 0.0F)); + + return LayerDefinition.create(meshdefinition, 64, 32); + } + + @Override + public void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { + + } + + @Override + public void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, int color) { + root.render(poseStack, vertexConsumer, packedLight, packedOverlay, color); + } +} \ No newline at end of file diff --git a/src/main/java/team/leomc/celebrations/client/renderer/PartyHatRenderer.java b/src/main/java/team/leomc/celebrations/client/renderer/PartyHatRenderer.java index cdec47e..2d8a283 100644 --- a/src/main/java/team/leomc/celebrations/client/renderer/PartyHatRenderer.java +++ b/src/main/java/team/leomc/celebrations/client/renderer/PartyHatRenderer.java @@ -14,6 +14,7 @@ @OnlyIn(Dist.CLIENT) public class PartyHatRenderer { public static final ResourceLocation PARTY_HAT = Celebrations.id("textures/entity/party_hat.png"); + public static final ResourceLocation NEWSPAPER_HAT = Celebrations.id("textures/entity/newspaper_hat.png"); public static void render(ResourceLocation texture, float scale, boolean overlay, PoseStack poseStack, MultiBufferSource buffer, int color, int packedLight) { VertexConsumer consumer = buffer.getBuffer(CRenderType.entityCutoutNoCullTriangles(texture)); @@ -86,6 +87,43 @@ public static void render(ResourceLocation texture, float scale, boolean overlay poseStack.popPose(); } + public static void renderNewspaperHat(float scale, PoseStack poseStack, MultiBufferSource buffer, int color, int packedLight) { + VertexConsumer consumer = buffer.getBuffer(CRenderType.entityCutoutNoCullTriangles(NEWSPAPER_HAT)); + + float hatRadiusX = 10.5f / 16f; + float hatRadiusZ = 8.5f / 16f; + float hatHeight = 0.5f; + + poseStack.pushPose(); + poseStack.scale(scale, scale, scale); + PoseStack.Pose pose = poseStack.last(); + + vertex(consumer, pose, hatRadiusX, 0, 0, 0, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, 0, hatRadiusZ, 0.25f, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, hatHeight, 0, 2.655f / 28f, 0.342f / 16f, color, packedLight); + + vertex(consumer, pose, hatRadiusX, 0, 0, 0.25f, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, 0, -hatRadiusZ, 0.25f * 2, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, hatHeight, 0, 2.655f / 28f + 0.25f, 0.342f / 16f, color, packedLight); + + vertex(consumer, pose, -hatRadiusX, 0, 0, 0.25f * 2, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, 0, hatRadiusZ, 0.25f * 3, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, hatHeight, 0, 2.655f / 28f + 0.25f * 2, 0.342f / 16f, color, packedLight); + + vertex(consumer, pose, -hatRadiusX, 0, 0, 0.25f * 3, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, 0, -hatRadiusZ, 0.25f * 4, 9f / 16f, color, packedLight); + vertex(consumer, pose, 0, hatHeight, 0, 2.655f / 28f + 0.25f * 3, 0.342f / 16f, color, packedLight); + + VertexConsumer quadsConsumer = buffer.getBuffer(RenderType.entityCutoutNoCull(NEWSPAPER_HAT)); + + vertex(quadsConsumer, pose, 0, 0, hatRadiusZ, 0, 9.387f / 16f, color, packedLight); + vertex(quadsConsumer, pose, hatRadiusX, 0, 0, 1.387f / 28f, 1, color, packedLight); + vertex(quadsConsumer, pose, 0, 0, -hatRadiusZ, 8 / 28f, 1, color, packedLight); + vertex(quadsConsumer, pose, -hatRadiusX, 0, 0, (7 - 0.387f) / 28f, 9.387f / 16f, color, packedLight); + + poseStack.popPose(); + } + private static void vertex(VertexConsumer vertexConsumer, PoseStack.Pose pose, float x, float y, float z, float uvX, float uvY, int color, int light) { vertexConsumer.addVertex(pose, x, y, z).setColor(color).setUv(uvX, uvY).setOverlay(OverlayTexture.NO_OVERLAY).setLight(light).setNormal(0.0F, 1.0F, 0.0F); } diff --git a/src/main/java/team/leomc/celebrations/client/renderer/CoupletRenderer.java b/src/main/java/team/leomc/celebrations/client/renderer/block/CoupletRenderer.java similarity index 98% rename from src/main/java/team/leomc/celebrations/client/renderer/CoupletRenderer.java rename to src/main/java/team/leomc/celebrations/client/renderer/block/CoupletRenderer.java index 5736d40..88f443c 100644 --- a/src/main/java/team/leomc/celebrations/client/renderer/CoupletRenderer.java +++ b/src/main/java/team/leomc/celebrations/client/renderer/block/CoupletRenderer.java @@ -1,4 +1,4 @@ -package team.leomc.celebrations.client.renderer; +package team.leomc.celebrations.client.renderer.block; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; diff --git a/src/main/java/team/leomc/celebrations/client/renderer/FuStickerRenderer.java b/src/main/java/team/leomc/celebrations/client/renderer/block/FuStickerRenderer.java similarity index 98% rename from src/main/java/team/leomc/celebrations/client/renderer/FuStickerRenderer.java rename to src/main/java/team/leomc/celebrations/client/renderer/block/FuStickerRenderer.java index 9907bb8..cc1c5b8 100644 --- a/src/main/java/team/leomc/celebrations/client/renderer/FuStickerRenderer.java +++ b/src/main/java/team/leomc/celebrations/client/renderer/block/FuStickerRenderer.java @@ -1,4 +1,4 @@ -package team.leomc.celebrations.client.renderer; +package team.leomc.celebrations.client.renderer.block; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; diff --git a/src/main/java/team/leomc/celebrations/client/renderer/HorizontalScrollRenderer.java b/src/main/java/team/leomc/celebrations/client/renderer/block/HorizontalScrollRenderer.java similarity index 98% rename from src/main/java/team/leomc/celebrations/client/renderer/HorizontalScrollRenderer.java rename to src/main/java/team/leomc/celebrations/client/renderer/block/HorizontalScrollRenderer.java index 5ead84b..359656d 100644 --- a/src/main/java/team/leomc/celebrations/client/renderer/HorizontalScrollRenderer.java +++ b/src/main/java/team/leomc/celebrations/client/renderer/block/HorizontalScrollRenderer.java @@ -1,4 +1,4 @@ -package team.leomc.celebrations.client.renderer; +package team.leomc.celebrations.client.renderer.block; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; diff --git a/src/main/java/team/leomc/celebrations/client/renderer/SignLikeRenderer.java b/src/main/java/team/leomc/celebrations/client/renderer/block/SignLikeRenderer.java similarity index 99% rename from src/main/java/team/leomc/celebrations/client/renderer/SignLikeRenderer.java rename to src/main/java/team/leomc/celebrations/client/renderer/block/SignLikeRenderer.java index b6b0c0d..b45407e 100644 --- a/src/main/java/team/leomc/celebrations/client/renderer/SignLikeRenderer.java +++ b/src/main/java/team/leomc/celebrations/client/renderer/block/SignLikeRenderer.java @@ -1,4 +1,4 @@ -package team.leomc.celebrations.client.renderer; +package team.leomc.celebrations.client.renderer.block; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; diff --git a/src/main/java/team/leomc/celebrations/client/renderer/entity/BalloonRenderer.java b/src/main/java/team/leomc/celebrations/client/renderer/entity/BalloonRenderer.java new file mode 100644 index 0000000..bca5be8 --- /dev/null +++ b/src/main/java/team/leomc/celebrations/client/renderer/entity/BalloonRenderer.java @@ -0,0 +1,45 @@ +package team.leomc.celebrations.client.renderer.entity; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import team.leomc.celebrations.Celebrations; +import team.leomc.celebrations.client.event.CClientEvents; +import team.leomc.celebrations.client.model.entity.BalloonModel; +import team.leomc.celebrations.entity.Balloon; + +@OnlyIn(Dist.CLIENT) +public class BalloonRenderer extends EntityRenderer { + private static final ResourceLocation ENTITY_TEXTURE = Celebrations.id("textures/entity/balloon.png"); + private final BalloonModel model; + + public BalloonRenderer(EntityRendererProvider.Context context) { + super(context); + model = new BalloonModel<>(context.bakeLayer(BalloonModel.LAYER_LOCATION)); + } + + @Override + public void render(Balloon entity, float yaw, float partialTicks, PoseStack poseStack, MultiBufferSource bufferSource, int light) { + poseStack.pushPose(); + poseStack.scale(-1.0F, -1.0F, 1.0F); + poseStack.translate(0.0F, -1.5F, 0.0F); + RenderType renderType = this.model.renderType(getTextureLocation(entity)); + VertexConsumer vertexConsumer = bufferSource.getBuffer(renderType); + this.model.renderToBuffer(poseStack, vertexConsumer, light, OverlayTexture.NO_OVERLAY, entity.getRenderColor()); + poseStack.popPose(); + CClientEvents.BALLOON_LIGHT.put(entity.getId(), light); + super.render(entity, yaw, partialTicks, poseStack, bufferSource, light); + } + + @Override + public ResourceLocation getTextureLocation(Balloon entity) { + return ENTITY_TEXTURE; + } +} diff --git a/src/main/java/team/leomc/celebrations/client/renderer/layer/PartyHatLayer.java b/src/main/java/team/leomc/celebrations/client/renderer/layer/PartyHatLayer.java index d73872b..e3ce9bd 100644 --- a/src/main/java/team/leomc/celebrations/client/renderer/layer/PartyHatLayer.java +++ b/src/main/java/team/leomc/celebrations/client/renderer/layer/PartyHatLayer.java @@ -26,10 +26,13 @@ public PartyHatLayer(RenderLayerParent renderer) { @Override public void render(PoseStack poseStack, MultiBufferSource buffer, int packedLight, T mob, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch) { + ItemStack stack = mob.getItemBySlot(EquipmentSlot.HEAD); + if (!stack.is(CItems.PARTY_HAT.get()) && !stack.isEmpty()) { + return; + } poseStack.pushPose(); getParentModel().translate(poseStack); - ItemStack stack = mob.getItemBySlot(EquipmentSlot.HEAD); - if (mob instanceof PartyHatWearer wearer && wearer.isWearingPartyHat() && !stack.is(CItems.PARTY_HAT.get())) { + if (mob instanceof PartyHatWearer wearer && wearer.isWearingPartyHat() && stack.isEmpty()) { stack = PartyHatUtils.getMobPartyHatItem(mob); } if (stack.is(CItems.PARTY_HAT.get())) { diff --git a/src/main/java/team/leomc/celebrations/data/gen/lang/CChineseLanguageProvider.java b/src/main/java/team/leomc/celebrations/data/gen/lang/CChineseLanguageProvider.java index 29e1e10..8ceb120 100644 --- a/src/main/java/team/leomc/celebrations/data/gen/lang/CChineseLanguageProvider.java +++ b/src/main/java/team/leomc/celebrations/data/gen/lang/CChineseLanguageProvider.java @@ -4,6 +4,7 @@ import net.neoforged.neoforge.common.data.LanguageProvider; import team.leomc.celebrations.Celebrations; import team.leomc.celebrations.registry.CBlocks; +import team.leomc.celebrations.registry.CEntities; import team.leomc.celebrations.registry.CItems; public class CChineseLanguageProvider extends LanguageProvider { @@ -39,6 +40,8 @@ protected void addTranslations() { add(CItems.RED_PAPER.get(), "红纸"); add(CItems.GOLD_POWDER.get(), "金粉"); add(CItems.PARTY_HAT.get(), "派对帽"); + add(CItems.BALLOON.get(), "气球"); + add(CEntities.BALLOON.get(), "气球"); add("party_hat_type." + Celebrations.ID + ".stripes", "横纹"); add("party_hat_type." + Celebrations.ID + ".tilt_stripes", "斜纹"); add("party_hat_type." + Celebrations.ID + ".dots", "点状纹"); diff --git a/src/main/java/team/leomc/celebrations/data/gen/lang/CEnglishLanguageProvider.java b/src/main/java/team/leomc/celebrations/data/gen/lang/CEnglishLanguageProvider.java index eec7aea..81c1a01 100644 --- a/src/main/java/team/leomc/celebrations/data/gen/lang/CEnglishLanguageProvider.java +++ b/src/main/java/team/leomc/celebrations/data/gen/lang/CEnglishLanguageProvider.java @@ -4,6 +4,7 @@ import net.neoforged.neoforge.common.data.LanguageProvider; import team.leomc.celebrations.Celebrations; import team.leomc.celebrations.registry.CBlocks; +import team.leomc.celebrations.registry.CEntities; import team.leomc.celebrations.registry.CItems; public class CEnglishLanguageProvider extends LanguageProvider { @@ -39,6 +40,8 @@ protected void addTranslations() { add(CItems.RED_PAPER.get(), "Red Paper"); add(CItems.GOLD_POWDER.get(), "Gold Powder"); add(CItems.PARTY_HAT.get(), "Party Hat"); + add(CItems.BALLOON.get(), "Balloon"); + add(CEntities.BALLOON.get(), "Balloon"); add("party_hat_type." + Celebrations.ID + ".stripes", " Stripes"); add("party_hat_type." + Celebrations.ID + ".tilt_stripes", " Tilt Stripes"); add("party_hat_type." + Celebrations.ID + ".dots", " Dots"); diff --git a/src/main/java/team/leomc/celebrations/data/gen/model/CItemModelProvider.java b/src/main/java/team/leomc/celebrations/data/gen/model/CItemModelProvider.java index 885b759..ac2338f 100644 --- a/src/main/java/team/leomc/celebrations/data/gen/model/CItemModelProvider.java +++ b/src/main/java/team/leomc/celebrations/data/gen/model/CItemModelProvider.java @@ -33,6 +33,7 @@ protected void registerModels() { basicItem(CItems.RED_PAPER.get()); basicItem(CItems.GOLD_POWDER.get()); partyHat(CItems.PARTY_HAT.get()); + balloon(CItems.BALLOON.get()); } private void partyHat(Item item) { @@ -56,6 +57,13 @@ private void partyHat(Item item) { .override().predicate(Celebrations.id("party_hat_type"), 0.3f).model(dots).end(); } + private void balloon(Item item) { + this.getBuilder(name(item)) + .parent(new ModelFile.UncheckedModelFile("item/generated")) + .texture("layer0", itemTexture(item).withSuffix("_leash")) + .texture("layer1", itemTexture(item)); + } + private void block(Item item) { withExistingParent(name(item), modLoc(ModelProvider.BLOCK_FOLDER + "/" + name(item))); } diff --git a/src/main/java/team/leomc/celebrations/entity/Balloon.java b/src/main/java/team/leomc/celebrations/entity/Balloon.java new file mode 100644 index 0000000..90a5054 --- /dev/null +++ b/src/main/java/team/leomc/celebrations/entity/Balloon.java @@ -0,0 +1,202 @@ +package team.leomc.celebrations.entity; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import team.leomc.celebrations.item.component.BalloonData; +import team.leomc.celebrations.registry.CDataComponents; +import team.leomc.celebrations.registry.CItems; +import team.leomc.celebrations.util.CMathUtil; + +import java.util.UUID; + +public class Balloon extends Entity { + private LivingEntity owner; + private UUID ownerId; + protected static final EntityDataAccessor OWNER_ID = SynchedEntityData.defineId(Balloon.class, EntityDataSerializers.INT); + private static final EntityDataAccessor ITEM_STACK = SynchedEntityData.defineId(Balloon.class, EntityDataSerializers.ITEM_STACK); + protected static final EntityDataAccessor MAIN_HAND = SynchedEntityData.defineId(Balloon.class, EntityDataSerializers.BOOLEAN); + + public Balloon(EntityType entityType, Level level) { + super(entityType, level); + this.noCulling = true; + } + + public LivingEntity getOwner() { + return owner; + } + + public void setOwner(LivingEntity owner) { + this.ownerId = owner.getUUID(); + this.owner = owner; + this.setOwnerId(owner.getId()); + } + + public int getOwnerId() { + return this.getEntityData().get(OWNER_ID); + } + + public void setOwnerId(int ownerId) { + this.getEntityData().set(OWNER_ID, ownerId); + } + + public void setItem(ItemStack stack) { + this.getEntityData().set(ITEM_STACK, stack.copyWithCount(1)); + } + + public ItemStack getItem() { + return this.getEntityData().get(ITEM_STACK); + } + + public void setMainHand(boolean mainHand) { + this.getEntityData().set(MAIN_HAND, mainHand); + } + + public boolean isMainHand() { + return this.getEntityData().get(MAIN_HAND); + } + + @Override + protected void defineSynchedData(SynchedEntityData.Builder builder) { + builder.define(OWNER_ID, -1) + .define(ITEM_STACK, CItems.BALLOON.get().getDefaultInstance()) + .define(MAIN_HAND, true); + } + + @Override + protected MovementEmission getMovementEmission() { + return MovementEmission.NONE; + } + + @Override + public void tick() { + super.tick(); + if (!level().isClientSide) { + if (owner == null && ownerId != null) { + if (((ServerLevel) this.level()).getEntity(ownerId) instanceof LivingEntity livingEntity) { + owner = livingEntity; + setOwnerId(livingEntity.getId()); + } + if (owner == null) { + ownerId = null; + } + } + if (owner != null) { + ItemStack stack = isMainHand() ? owner.getMainHandItem() : owner.getOffhandItem(); + BalloonData data = stack.get(CDataComponents.BALLOON_DATA.get()); + if (data == null) { + data = BalloonData.DEFAULT; + } + setCustomName(data.name()); + setCustomNameVisible(!data.name().getString().isEmpty()); + } + } else { + owner = level().getEntity(getOwnerId()) instanceof LivingEntity livingEntity ? livingEntity : null; + if (owner instanceof BalloonOwner balloonOwner) { + if (isMainHand()) { + balloonOwner.setMainHandBalloon(this); + } else { + balloonOwner.setOffhandBalloon(this); + } + } + } + this.xo = this.getX(); + this.yo = this.getY(); + this.zo = this.getZ(); + if (owner != null) { + if (!level().isClientSide && owner instanceof BalloonOwner balloonOwner && (isMainHand() ? balloonOwner.getMainHandBalloon() : balloonOwner.getOffhandBalloon()) != this) { + discard(); + } + if (!level().isClientSide && isMainHand() && !ItemStack.isSameItemSameComponents(getItem(), owner.getMainHandItem())) { + discard(); + } + if (!level().isClientSide && !isMainHand() && !ItemStack.isSameItemSameComponents(getItem(), owner.getOffhandItem())) { + discard(); + } + Vec3 targetPos = getTargetPos(1); + Vec3 diff = targetPos.subtract(position()); + double length = diff.length(); + this.setDeltaMovement(CMathUtil.lerpVec(targetPos.distanceTo(position()) > 5 ? 0.25f : 0.05f, getDeltaMovement(), diff.normalize().scale(Math.min(length, 0.3)))); + } else { + Vec3 movement = this.getDeltaMovement(); + this.setDeltaMovement(movement.x, Math.min(movement.y + 0.02, 0.08), movement.z); + } + this.move(MoverType.SELF, this.getDeltaMovement()); + } + + public Vec3 getTargetPos(float partialTicks) { + return CMathUtil.rotationToPosition(owner.getPosition(partialTicks).add(0, owner.getBbHeight() * 1.3f, 0), owner.getBbWidth() * 1.2f, 0, (Mth.lerp(partialTicks, owner.yRotO, owner.getYRot())) + 90 + ((isMainHand() && owner.getMainArm() == HumanoidArm.RIGHT) || (!isMainHand() && owner.getMainArm() == HumanoidArm.LEFT) ? 55 : -55)); + } + + public int getRenderColor() { + BalloonData data = getItem().get(CDataComponents.BALLOON_DATA.get()); + if (data != null) { + return data.color().getTextureDiffuseColor(); + } + return -1; + } + + @Override + public boolean isPickable() { + return !this.isRemoved(); + } + + @Override + public InteractionResult interact(Player player, InteractionHand hand) { + ItemStack stack = player.getItemInHand(hand); + Component component = stack.get(DataComponents.CUSTOM_NAME); + if (stack.is(Items.NAME_TAG) && component != null) { + if (!level().isClientSide) { + ItemStack balloon = isMainHand() ? owner.getMainHandItem() : owner.getOffhandItem(); + BalloonData data = balloon.get(CDataComponents.BALLOON_DATA.get()); + if (data == null) { + data = BalloonData.DEFAULT; + } + balloon.set(CDataComponents.BALLOON_DATA.get(), data.withName(component)); + setItem(balloon); + } + stack.consume(1, player); + return InteractionResult.sidedSuccess(player.level().isClientSide); + } + return InteractionResult.PASS; + } + + @Override + public void onClientRemoval() { + super.onClientRemoval(); + if (owner instanceof BalloonOwner balloonOwner) { + if (isMainHand()) { + balloonOwner.setMainHandBalloon(null); + } else { + balloonOwner.setOffhandBalloon(null); + } + } + } + + @Override + protected void readAdditionalSaveData(CompoundTag compoundTag) { + if (compoundTag.hasUUID("owner")) { + ownerId = compoundTag.getUUID("owner"); + } + } + + @Override + protected void addAdditionalSaveData(CompoundTag compoundTag) { + if (owner != null) { + compoundTag.putUUID("owner", owner.getUUID()); + } + } +} diff --git a/src/main/java/team/leomc/celebrations/entity/BalloonOwner.java b/src/main/java/team/leomc/celebrations/entity/BalloonOwner.java new file mode 100644 index 0000000..a442a5f --- /dev/null +++ b/src/main/java/team/leomc/celebrations/entity/BalloonOwner.java @@ -0,0 +1,11 @@ +package team.leomc.celebrations.entity; + +public interface BalloonOwner { + Balloon getMainHandBalloon(); + + Balloon getOffhandBalloon(); + + void setMainHandBalloon(Balloon balloon); + + void setOffhandBalloon(Balloon balloon); +} diff --git a/src/main/java/team/leomc/celebrations/event/CEvents.java b/src/main/java/team/leomc/celebrations/event/CEvents.java index a1fffee..82d6dce 100644 --- a/src/main/java/team/leomc/celebrations/event/CEvents.java +++ b/src/main/java/team/leomc/celebrations/event/CEvents.java @@ -12,6 +12,7 @@ import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.entity.npc.VillagerProfession; import net.minecraft.world.entity.npc.WanderingTrader; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; @@ -24,7 +25,10 @@ import team.leomc.celebrations.ai.VillagerCelebrationAI; import team.leomc.celebrations.block.entity.LanternBlockEntity; import team.leomc.celebrations.command.CelebrationsCommand; +import team.leomc.celebrations.entity.Balloon; +import team.leomc.celebrations.entity.BalloonOwner; import team.leomc.celebrations.network.UpdateMobPartyHatPayload; +import team.leomc.celebrations.registry.CEntities; import team.leomc.celebrations.registry.CItems; import team.leomc.celebrations.util.CelebrationUtils; import team.leomc.celebrations.util.LanternUtils; @@ -174,5 +178,31 @@ private static void onPostEntityTick(EntityTickEvent.Post event) { } } } + if (event.getEntity() instanceof Player player && player instanceof BalloonOwner owner && !player.level().isClientSide) { + if (owner.getMainHandBalloon() != null && (owner.getMainHandBalloon().getOwner() != player || !owner.getMainHandBalloon().isMainHand() || owner.getMainHandBalloon().isRemoved())) { + owner.setMainHandBalloon(null); + } + if (owner.getOffhandBalloon() != null && (owner.getOffhandBalloon().getOwner() != player || owner.getOffhandBalloon().isMainHand() || owner.getOffhandBalloon().isRemoved())) { + owner.setOffhandBalloon(null); + } + if (player.getMainHandItem().is(CItems.BALLOON.get()) && (owner.getMainHandBalloon() == null || owner.getMainHandBalloon().isRemoved() || owner.getMainHandBalloon().getOwner() != player)) { + Balloon balloon = new Balloon(CEntities.BALLOON.get(), player.level()); + balloon.setOwner(player); + balloon.setItem(player.getMainHandItem()); + balloon.setMainHand(true); + balloon.setPos(balloon.getTargetPos(1)); + player.level().addFreshEntity(balloon); + owner.setMainHandBalloon(balloon); + } else if (player.getOffhandItem().is(CItems.BALLOON.get()) && (owner.getOffhandBalloon() == null || owner.getOffhandBalloon().isRemoved() || owner.getOffhandBalloon().getOwner() != player)) { + Balloon balloon = new Balloon(CEntities.BALLOON.get(), player.level()); + balloon.setOwner(player); + balloon.setItem(player.getOffhandItem()); + balloon.setMainHand(false); + balloon.setPos(balloon.getTargetPos(1)); + player.level().addFreshEntity(balloon); + owner.setOffhandBalloon(balloon); + } + + } } } diff --git a/src/main/java/team/leomc/celebrations/item/LanternItem.java b/src/main/java/team/leomc/celebrations/item/LanternItem.java index d6f2d4d..9987db3 100644 --- a/src/main/java/team/leomc/celebrations/item/LanternItem.java +++ b/src/main/java/team/leomc/celebrations/item/LanternItem.java @@ -68,7 +68,7 @@ public InteractionResultHolder use(Level level, Player player, Intera if (lantern != null) { lantern = new Lantern(lantern.effects(), newGift, player.getName()); stack.applyComponentsAndValidate(DataComponentPatch.builder().set(CDataComponents.LANTERN.get(), lantern).build()); - if (!player.getAbilities().instabuild) { + if (!player.hasInfiniteMaterials()) { player.setItemInHand(oppositeHand, ItemStack.EMPTY); } } diff --git a/src/main/java/team/leomc/celebrations/item/component/BalloonData.java b/src/main/java/team/leomc/celebrations/item/component/BalloonData.java new file mode 100644 index 0000000..42da869 --- /dev/null +++ b/src/main/java/team/leomc/celebrations/item/component/BalloonData.java @@ -0,0 +1,54 @@ +package team.leomc.celebrations.item.component; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.ItemStack; + +import java.util.Objects; + +public record BalloonData(DyeColor color, Component name, ItemStack attached) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + DyeColor.CODEC.fieldOf("color").forGetter(BalloonData::color), + ComponentSerialization.CODEC.fieldOf("name").forGetter(BalloonData::name), + ItemStack.OPTIONAL_CODEC.fieldOf("attached").forGetter(BalloonData::attached) + ).apply(instance, BalloonData::new)); + + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistries(CODEC); + + public static final BalloonData DEFAULT = new BalloonData(DyeColor.WHITE, Component.empty(), ItemStack.EMPTY); + + public static BalloonData fromTag(Tag tag, HolderLookup.Provider provider) { + return CODEC.parse(provider.createSerializationContext(NbtOps.INSTANCE), tag).resultOrPartial().orElse(DEFAULT); + } + + public Tag toTag(HolderLookup.Provider provider) { + return CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), this).resultOrPartial().orElseGet(CompoundTag::new); + } + + public BalloonData withName(Component name) { + return new BalloonData(color(), name, attached()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BalloonData balloonData = (BalloonData) o; + return color == balloonData.color && Objects.equals(name, balloonData.name) && Objects.equals(attached.getItem(), balloonData.attached.getItem()) && Objects.equals(attached.getCount(), balloonData.attached.getCount()) && Objects.equals(attached.getComponents(), balloonData.attached.getComponents()); + } + + @Override + public int hashCode() { + return Objects.hash(color, name, attached); + } +} diff --git a/src/main/java/team/leomc/celebrations/mixin/MobMixin.java b/src/main/java/team/leomc/celebrations/mixin/MobMixin.java index 24a3107..0d96767 100644 --- a/src/main/java/team/leomc/celebrations/mixin/MobMixin.java +++ b/src/main/java/team/leomc/celebrations/mixin/MobMixin.java @@ -2,6 +2,7 @@ import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.Mob; +import net.minecraft.world.item.ArmorItem; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -20,7 +21,8 @@ public abstract class MobMixin implements PartyHatWearer { @Inject(method = "getItemBySlot", at = @At("RETURN"), cancellable = true) protected void getItemBySlot(EquipmentSlot slot, CallbackInfoReturnable cir) { Mob mob = ((Mob) (Object) this); - if (celebrations$wearingPartyHat && isWearingPartyHat() && mob.level().isClientSide) { + ItemStack original = cir.getReturnValue(); + if ((original.isEmpty() || original.getItem() instanceof ArmorItem) && celebrations$wearingPartyHat && isWearingPartyHat() && mob.level().isClientSide) { cir.setReturnValue(PartyHatUtils.getMobPartyHatItem(mob)); } } diff --git a/src/main/java/team/leomc/celebrations/mixin/PlayerMixin.java b/src/main/java/team/leomc/celebrations/mixin/PlayerMixin.java new file mode 100644 index 0000000..38f5c0b --- /dev/null +++ b/src/main/java/team/leomc/celebrations/mixin/PlayerMixin.java @@ -0,0 +1,35 @@ +package team.leomc.celebrations.mixin; + +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import team.leomc.celebrations.entity.Balloon; +import team.leomc.celebrations.entity.BalloonOwner; + +@Mixin(Player.class) +public abstract class PlayerMixin implements BalloonOwner { + @Unique + private Balloon celebrations$mainHandBalloon; + @Unique + private Balloon celebrations$offhandBalloon; + + @Override + public Balloon getMainHandBalloon() { + return celebrations$mainHandBalloon; + } + + @Override + public Balloon getOffhandBalloon() { + return celebrations$offhandBalloon; + } + + @Override + public void setMainHandBalloon(Balloon balloon) { + celebrations$mainHandBalloon = balloon; + } + + @Override + public void setOffhandBalloon(Balloon balloon) { + celebrations$offhandBalloon = balloon; + } +} diff --git a/src/main/java/team/leomc/celebrations/mixin/client/CustomHeadLayerMixin.java b/src/main/java/team/leomc/celebrations/mixin/client/CustomHeadLayerMixin.java index 868a184..58cfc12 100644 --- a/src/main/java/team/leomc/celebrations/mixin/client/CustomHeadLayerMixin.java +++ b/src/main/java/team/leomc/celebrations/mixin/client/CustomHeadLayerMixin.java @@ -9,6 +9,8 @@ import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.entity.npc.VillagerProfession; import net.minecraft.world.item.ItemStack; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; @@ -32,13 +34,19 @@ protected void render(PoseStack poseStack, MultiBufferSource buffer, int packedL if (livingEntity instanceof PartyHatWearer wearer && wearer.isWearingPartyHat() && livingEntity instanceof Mob mob && !stack.is(CItems.PARTY_HAT.get())) { stack = PartyHatUtils.getMobPartyHatItem(mob); } + boolean newspaper = livingEntity instanceof Villager villager && villager.getVillagerData().getProfession() == VillagerProfession.NITWIT; if (stack.is(CItems.PARTY_HAT.get())) { poseStack.pushPose(); poseStack.translate(0, 0.375f, 0); - PartyHatRenderer.render(PartyHatRenderer.PARTY_HAT, 1f / 0.625f, false, poseStack, buffer, FastColor.ARGB32.colorFromFloat(1, 1, 1, 1), packedLight); - PartyHat hat = stack.get(CDataComponents.PART_HAT.get()); - if (hat != null) { - PartyHatRenderer.render(hat.getTextureLocation(), 1f / 0.625f, true, poseStack, buffer, hat.color().getTextureDiffuseColor(), packedLight); + if (newspaper) { + poseStack.translate(0, 0.02f, 0); + PartyHatRenderer.renderNewspaperHat(1f, poseStack, buffer, FastColor.ARGB32.colorFromFloat(1, 1, 1, 1), packedLight); + } else { + PartyHatRenderer.render(PartyHatRenderer.PARTY_HAT, 1f / 0.625f, false, poseStack, buffer, FastColor.ARGB32.colorFromFloat(1, 1, 1, 1), packedLight); + PartyHat hat = stack.get(CDataComponents.PART_HAT.get()); + if (hat != null) { + PartyHatRenderer.render(hat.getTextureLocation(), 1f / 0.625f, true, poseStack, buffer, hat.color().getTextureDiffuseColor(), packedLight); + } } poseStack.popPose(); } diff --git a/src/main/java/team/leomc/celebrations/mixin/client/ItemInHandRendererMixin.java b/src/main/java/team/leomc/celebrations/mixin/client/ItemInHandRendererMixin.java index 892b47b..df3b4c6 100644 --- a/src/main/java/team/leomc/celebrations/mixin/client/ItemInHandRendererMixin.java +++ b/src/main/java/team/leomc/celebrations/mixin/client/ItemInHandRendererMixin.java @@ -3,6 +3,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.renderer.ItemInHandRenderer; import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.world.entity.HumanoidArm; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; @@ -12,6 +13,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import team.leomc.celebrations.entity.BalloonOwner; import team.leomc.celebrations.registry.CItems; @OnlyIn(Dist.CLIENT) @@ -22,5 +24,23 @@ protected void renderItem(LivingEntity entity, ItemStack itemStack, ItemDisplayC if (displayContext == ItemDisplayContext.HEAD && itemStack.is(CItems.PARTY_HAT.get())) { ci.cancel(); } + if (entity instanceof BalloonOwner owner) { + if (owner.getMainHandBalloon() != null) { + if (entity.getMainArm() == HumanoidArm.RIGHT && (displayContext == ItemDisplayContext.FIRST_PERSON_RIGHT_HAND || displayContext == ItemDisplayContext.THIRD_PERSON_RIGHT_HAND)) { + ci.cancel(); + } + if (entity.getMainArm() == HumanoidArm.LEFT && (displayContext == ItemDisplayContext.FIRST_PERSON_LEFT_HAND || displayContext == ItemDisplayContext.THIRD_PERSON_LEFT_HAND)) { + ci.cancel(); + } + } + if (owner.getOffhandBalloon() != null) { + if (entity.getMainArm() == HumanoidArm.LEFT && (displayContext == ItemDisplayContext.FIRST_PERSON_RIGHT_HAND || displayContext == ItemDisplayContext.THIRD_PERSON_RIGHT_HAND)) { + ci.cancel(); + } + if (entity.getMainArm() == HumanoidArm.RIGHT && (displayContext == ItemDisplayContext.FIRST_PERSON_LEFT_HAND || displayContext == ItemDisplayContext.THIRD_PERSON_LEFT_HAND)) { + ci.cancel(); + } + } + } } } diff --git a/src/main/java/team/leomc/celebrations/mixin/client/PlayerRendererMixin.java b/src/main/java/team/leomc/celebrations/mixin/client/PlayerRendererMixin.java new file mode 100644 index 0000000..f4aadef --- /dev/null +++ b/src/main/java/team/leomc/celebrations/mixin/client/PlayerRendererMixin.java @@ -0,0 +1,49 @@ +package team.leomc.celebrations.mixin.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.player.PlayerRenderer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.phys.Vec3; +import org.joml.Quaternionf; +import org.joml.Vector4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import team.leomc.celebrations.client.event.CClientEvents; + +@Mixin(PlayerRenderer.class) +public abstract class PlayerRendererMixin { + @Inject(method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At("TAIL")) + private void render(AbstractClientPlayer player, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, CallbackInfo ci) { + PlayerRenderer renderer = (PlayerRenderer) (Object) this; + Vec3 entityPos = new Vec3(Mth.lerp(partialTicks, player.xo, player.getX()), Mth.lerp(partialTicks, player.yo, player.getY()), Mth.lerp(partialTicks, player.zo, player.getZ())); + double scale = 1; + if (player.getAttributes().hasAttribute(Attributes.SCALE)) { + AttributeInstance instance = player.getAttribute(Attributes.SCALE); + if (instance != null) { + scale = instance.getValue(); + } + } + for (int i = 0; i < 2; i++) { + boolean left = i == 0; + PoseStack stack = new PoseStack(); + stack.translate(entityPos.x, entityPos.y, entityPos.z); + stack.mulPose(new Quaternionf().rotationY((-Mth.lerp(partialTicks, player.yBodyRotO, player.yBodyRot) + 180.0F) * Mth.DEG_TO_RAD)); + stack.scale(-1, -1, 1); + stack.translate(0, -1.5f, 0); + + (left ? renderer.getModel().leftArm : renderer.getModel().rightArm).translateAndRotate(stack); + stack.translate(0, 0.55, 0); + + Vector4f vec = new Vector4f(0, 0, 0, 1).mul(stack.last().pose()); + Vec3 pos = new Vec3(vec.x(), vec.y(), vec.z()); + Vec3 subtract = pos.subtract(entityPos); + (left ? CClientEvents.PLAYER_LEFT_HAND_POS : CClientEvents.PLAYER_RIGHT_HAND_POS).put(player.getId(), entityPos.add(subtract.scale(scale))); + } + } +} diff --git a/src/main/java/team/leomc/celebrations/registry/CDataComponents.java b/src/main/java/team/leomc/celebrations/registry/CDataComponents.java index 8933963..b2137e1 100644 --- a/src/main/java/team/leomc/celebrations/registry/CDataComponents.java +++ b/src/main/java/team/leomc/celebrations/registry/CDataComponents.java @@ -7,6 +7,7 @@ import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; import team.leomc.celebrations.Celebrations; +import team.leomc.celebrations.item.component.BalloonData; import team.leomc.celebrations.item.component.Lantern; import team.leomc.celebrations.item.component.PartyHat; @@ -16,4 +17,5 @@ public class CDataComponents { public static final DeferredHolder, DataComponentType> LANTERN = DATA_COMPONENTS.register("lantern", () -> DataComponentType.builder().persistent(Lantern.CODEC).networkSynchronized(Lantern.STREAM_CODEC).build()); public static final DeferredHolder, DataComponentType> FIREWORK_AMOUNT = DATA_COMPONENTS.register("firework_amount", () -> DataComponentType.builder().persistent(Codec.INT).networkSynchronized(ByteBufCodecs.INT).build()); public static final DeferredHolder, DataComponentType> PART_HAT = DATA_COMPONENTS.register("party_hat", () -> DataComponentType.builder().persistent(PartyHat.CODEC).networkSynchronized(PartyHat.STREAM_CODEC).build()); + public static final DeferredHolder, DataComponentType> BALLOON_DATA = DATA_COMPONENTS.register("balloon_data", () -> DataComponentType.builder().persistent(BalloonData.CODEC).networkSynchronized(BalloonData.STREAM_CODEC).build()); } diff --git a/src/main/java/team/leomc/celebrations/registry/CEntities.java b/src/main/java/team/leomc/celebrations/registry/CEntities.java new file mode 100644 index 0000000..e56a5c9 --- /dev/null +++ b/src/main/java/team/leomc/celebrations/registry/CEntities.java @@ -0,0 +1,15 @@ +package team.leomc.celebrations.registry; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import team.leomc.celebrations.Celebrations; +import team.leomc.celebrations.entity.Balloon; + +public class CEntities { + public static final DeferredRegister> ENTITY_TYPES = DeferredRegister.create(BuiltInRegistries.ENTITY_TYPE, Celebrations.ID); + + public static final DeferredHolder, EntityType> BALLOON = ENTITY_TYPES.register("balloon", () -> EntityType.Builder.of(Balloon::new, MobCategory.MISC).sized(0.5f, 0.75f).clientTrackingRange(10).updateInterval(1).build(Celebrations.id("balloon").toString())); +} diff --git a/src/main/java/team/leomc/celebrations/registry/CItems.java b/src/main/java/team/leomc/celebrations/registry/CItems.java index 8dcc96d..9ddbc58 100644 --- a/src/main/java/team/leomc/celebrations/registry/CItems.java +++ b/src/main/java/team/leomc/celebrations/registry/CItems.java @@ -35,4 +35,6 @@ public class CItems { public static final DeferredItem GOLD_POWDER = ITEMS.register("gold_powder", () -> new DyeItem(DyeColor.YELLOW, new Item.Properties())); public static final DeferredItem PARTY_HAT = ITEMS.register("party_hat", () -> new PartyHatItem(new Item.Properties().stacksTo(1))); + + public static final DeferredItem BALLOON = ITEMS.register("balloon", () -> new Item(new Item.Properties().stacksTo(1))); } diff --git a/src/main/java/team/leomc/celebrations/util/CMathUtil.java b/src/main/java/team/leomc/celebrations/util/CMathUtil.java new file mode 100644 index 0000000..f7819d4 --- /dev/null +++ b/src/main/java/team/leomc/celebrations/util/CMathUtil.java @@ -0,0 +1,46 @@ +package team.leomc.celebrations.util; + +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +public class CMathUtil { + public static float positionToPitch(Vec3 start, Vec3 end) { + return positionToPitch(end.subtract(start)); + } + + public static float positionToYaw(Vec3 start, Vec3 end) { + return positionToYaw(end.subtract(start)); + } + + public static float positionToPitch(Vec3 vec3) { + return positionToPitch(vec3.x, vec3.y, vec3.z); + } + + public static float positionToYaw(Vec3 vec3) { + return positionToYaw(vec3.x, vec3.z); + } + + public static float positionToPitch(double diffX, double diffY, double diffZ) { + double horizontalDist = Math.sqrt(diffX * diffX + diffZ * diffZ); + return !(Math.abs(diffY) > (double) 1.0E-5F) && !(Math.abs(horizontalDist) > (double) 1.0E-5F) ? 0 : (float) ((Mth.atan2(diffY, horizontalDist) * Mth.RAD_TO_DEG)); + } + + public static float positionToYaw(double diffX, double diffZ) { + return !(Math.abs(diffZ) > (double) 1.0E-5F) && !(Math.abs(diffX) > (double) 1.0E-5F) ? 0 : (float) (Mth.atan2(diffZ, diffX) * Mth.RAD_TO_DEG); + } + + public static Vec3 rotationToPosition(float radius, float pitch, float yaw) { + double endPosX = radius * Math.cos(yaw * Mth.DEG_TO_RAD) * Math.cos(pitch * Mth.DEG_TO_RAD); + double endPosY = radius * Math.sin(pitch * Mth.DEG_TO_RAD); + double endPosZ = radius * Math.sin(yaw * Mth.DEG_TO_RAD) * Math.cos(pitch * Mth.DEG_TO_RAD); + return new Vec3(endPosX, endPosY, endPosZ); + } + + public static Vec3 rotationToPosition(Vec3 startPos, float radius, float pitch, float yaw) { + return startPos.add(rotationToPosition(radius, pitch, yaw)); + } + + public static Vec3 lerpVec(float progress, Vec3 from, Vec3 to) { + return new Vec3(Mth.lerp(progress, from.x, to.x), Mth.lerp(progress, from.y, to.y), Mth.lerp(progress, from.z, to.z)); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index e411a3f..65d79ec 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1 +1,3 @@ -public net.minecraft.world.entity.ai.Brain availableBehaviorsByPriority \ No newline at end of file +public net.minecraft.world.entity.ai.Brain availableBehaviorsByPriority + +public net.minecraft.client.renderer.LevelRenderer renderBuffers \ No newline at end of file diff --git a/src/main/resources/assets/celebrations/textures/entity/balloon.png b/src/main/resources/assets/celebrations/textures/entity/balloon.png new file mode 100644 index 0000000..0a980cc Binary files /dev/null and b/src/main/resources/assets/celebrations/textures/entity/balloon.png differ diff --git a/src/main/resources/assets/celebrations/textures/entity/newspaper_hat.png b/src/main/resources/assets/celebrations/textures/entity/newspaper_hat.png new file mode 100644 index 0000000..ec90cfd Binary files /dev/null and b/src/main/resources/assets/celebrations/textures/entity/newspaper_hat.png differ diff --git a/src/main/resources/assets/celebrations/textures/item/balloon.png b/src/main/resources/assets/celebrations/textures/item/balloon.png new file mode 100644 index 0000000..dca6a39 Binary files /dev/null and b/src/main/resources/assets/celebrations/textures/item/balloon.png differ diff --git a/src/main/resources/assets/celebrations/textures/item/balloon_leash.png b/src/main/resources/assets/celebrations/textures/item/balloon_leash.png new file mode 100644 index 0000000..0c86475 Binary files /dev/null and b/src/main/resources/assets/celebrations/textures/item/balloon_leash.png differ diff --git a/src/main/resources/celebrations.mixins.json b/src/main/resources/celebrations.mixins.json index 1bd9bd8..1ee4667 100644 --- a/src/main/resources/celebrations.mixins.json +++ b/src/main/resources/celebrations.mixins.json @@ -8,6 +8,7 @@ "CakeBlockMixin", "CelebrateVillagersSurvivedRaidMixin", "MobMixin", + "PlayerMixin", "VillagerMixin" ], "client": [ @@ -15,6 +16,7 @@ "client.AllayRendererMixin", "client.CustomHeadLayerMixin", "client.ItemInHandRendererMixin", + "client.PlayerRendererMixin", "client.VexModelMixin", "client.VexRendererMixin" ],