diff --git a/README.md b/README.md index de1d5da..6e4457a 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If you want to talk about ViaBedrock or learn more about it you can join my [Dis - [x] Form GUIs - [x] Scoreboard - [x] Titles -- [ ] Bossbar +- [x] Bossbar - [x] Player list - [x] Command suggestions - [x] Sounds (No mob ambient sounds yet) diff --git a/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java b/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java index 3edab6a..5d33b8a 100644 --- a/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java +++ b/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java @@ -19,7 +19,11 @@ import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5; +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.type.Types; +import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import net.raphimc.viabedrock.protocol.BedrockProtocol; +import net.raphimc.viabedrock.protocol.data.enums.java.BossEventOperationType; import net.raphimc.viabedrock.protocol.model.Position3f; import java.util.UUID; @@ -44,6 +48,7 @@ public class Entity { protected boolean onGround; protected String name; protected int age; + protected boolean hasBossBar; public Entity(final UserConnection user, final long uniqueId, final long runtimeId, final int javaId, final UUID javaUuid, final EntityTypes1_20_5 type) { this.user = user; @@ -59,6 +64,13 @@ public void tick() { } public void remove() { + if (this.hasBossBar) { + this.hasBossBar = false; + final PacketWrapper bossEvent = PacketWrapper.create(ClientboundPackets1_21.BOSS_EVENT, this.user); + bossEvent.write(Types.UUID, this.javaUuid()); // uuid + bossEvent.write(Types.VAR_INT, BossEventOperationType.REMOVE.ordinal()); // operation + bossEvent.send(BedrockProtocol.class); + } } public float eyeOffset() { @@ -121,6 +133,14 @@ public int age() { return this.age; } + public boolean hasBossBar() { + return this.hasBossBar; + } + + public void setHasBossBar(final boolean hasBossBar) { + this.hasBossBar = hasBossBar; + } + public final int getJavaEntityDataIndex(final String fieldName) { final int index = BedrockProtocol.MAPPINGS.getJavaEntityData().get(this.type).indexOf(fieldName); if (index == -1) { diff --git a/src/main/java/net/raphimc/viabedrock/api/model/entity/PlayerEntity.java b/src/main/java/net/raphimc/viabedrock/api/model/entity/PlayerEntity.java index af742ee..235a605 100644 --- a/src/main/java/net/raphimc/viabedrock/api/model/entity/PlayerEntity.java +++ b/src/main/java/net/raphimc/viabedrock/api/model/entity/PlayerEntity.java @@ -77,19 +77,16 @@ public final void updateName(final String name) { setPlayerTeam.send(BedrockProtocol.class); } - public final void deleteTeam() { + @Override + public void remove() { + super.remove(); + final PacketWrapper setPlayerTeam = PacketWrapper.create(ClientboundPackets1_21.SET_PLAYER_TEAM, this.user); setPlayerTeam.write(Types.STRING, "vb_" + this.javaId); // team name setPlayerTeam.write(Types.BYTE, (byte) PlayerTeamAction.REMOVE.ordinal()); // mode setPlayerTeam.send(BedrockProtocol.class); } - @Override - public void remove() { - super.remove(); - this.deleteTeam(); - } - @Override public float eyeOffset() { return 1.62F; diff --git a/src/main/java/net/raphimc/viabedrock/api/util/MathUtil.java b/src/main/java/net/raphimc/viabedrock/api/util/MathUtil.java index f3a8339..2546ad3 100644 --- a/src/main/java/net/raphimc/viabedrock/api/util/MathUtil.java +++ b/src/main/java/net/raphimc/viabedrock/api/util/MathUtil.java @@ -47,6 +47,10 @@ public static int clamp(final int value, final int min, final int max) { } } + public static int getOrFallback(final int value, final int min, final int max, final int fallback) { + return value < min || value > max ? fallback : value; + } + public static byte float2Byte(final float f) { return (byte) (f * 256F / 360F); } diff --git a/src/main/java/net/raphimc/viabedrock/api/util/TextUtil.java b/src/main/java/net/raphimc/viabedrock/api/util/TextUtil.java index 81d6ce5..6d20920 100644 --- a/src/main/java/net/raphimc/viabedrock/api/util/TextUtil.java +++ b/src/main/java/net/raphimc/viabedrock/api/util/TextUtil.java @@ -25,7 +25,7 @@ import net.lenni0451.mcstructs_bedrock.text.BedrockTextFormatting; import net.raphimc.viabedrock.protocol.data.ProtocolConstants; -import java.util.HashSet; +import java.util.EnumSet; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -81,7 +81,7 @@ public static ATextComponent stringToTextComponent(final String text) { */ private static String appendFormattingCodesAfterColorCode(final String s) { final char[] chars = s.toCharArray(); - final Set styles = new HashSet<>(); + final Set styles = EnumSet.noneOf(BedrockTextFormatting.class); final StringBuilder out = new StringBuilder(); for (int i = 0; i < chars.length; i++) { diff --git a/src/main/java/net/raphimc/viabedrock/protocol/data/enums/java/BossEventOperationType.java b/src/main/java/net/raphimc/viabedrock/protocol/data/enums/java/BossEventOperationType.java new file mode 100644 index 0000000..477597a --- /dev/null +++ b/src/main/java/net/raphimc/viabedrock/protocol/data/enums/java/BossEventOperationType.java @@ -0,0 +1,29 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2023-2024 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viabedrock.protocol.data.enums.java; + +public enum BossEventOperationType { + + ADD, + REMOVE, + UPDATE_PROGRESS, + UPDATE_NAME, + UPDATE_STYLE, + UPDATE_PROPERTIES, + +} diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java index d3f718e..05cd971 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java @@ -28,19 +28,14 @@ import net.lenni0451.mcstructs_bedrock.text.serializer.BedrockComponentSerializer; import net.lenni0451.mcstructs_bedrock.text.utils.BedrockTranslator; import net.raphimc.viabedrock.ViaBedrock; +import net.raphimc.viabedrock.api.model.entity.Entity; import net.raphimc.viabedrock.api.model.scoreboard.ScoreboardEntry; import net.raphimc.viabedrock.api.model.scoreboard.ScoreboardObjective; -import net.raphimc.viabedrock.api.util.BitSets; -import net.raphimc.viabedrock.api.util.PacketFactory; -import net.raphimc.viabedrock.api.util.StringUtil; -import net.raphimc.viabedrock.api.util.TextUtil; +import net.raphimc.viabedrock.api.util.*; import net.raphimc.viabedrock.protocol.BedrockProtocol; import net.raphimc.viabedrock.protocol.ClientboundBedrockPackets; import net.raphimc.viabedrock.protocol.data.enums.bedrock.*; -import net.raphimc.viabedrock.protocol.data.enums.java.CustomChatCompletionsAction; -import net.raphimc.viabedrock.protocol.data.enums.java.ObjectiveCriteriaRenderType; -import net.raphimc.viabedrock.protocol.data.enums.java.PlayerInfoUpdateAction; -import net.raphimc.viabedrock.protocol.data.enums.java.ScoreboardObjectiveAction; +import net.raphimc.viabedrock.protocol.data.enums.java.*; import net.raphimc.viabedrock.protocol.model.SkinData; import net.raphimc.viabedrock.protocol.provider.SkinProvider; import net.raphimc.viabedrock.protocol.storage.*; @@ -348,6 +343,68 @@ protected void register() { handler(wrapper -> wrapper.user().get(ScoreboardTracker.class).removeObjective(wrapper.get(Types.STRING, 0))); } }); + protocol.registerClientbound(ClientboundBedrockPackets.BOSS_EVENT, ClientboundPackets1_21.BOSS_EVENT, wrapper -> { + final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); + final long bossUniqueEntityId = wrapper.read(BedrockTypes.VAR_LONG); // boss unique entity id + final int rawUpdateType = wrapper.read(BedrockTypes.UNSIGNED_VAR_INT); // update type + final BossEventUpdateType updateType = BossEventUpdateType.getByValue(rawUpdateType); + if (updateType == null) { + ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Unknown BossEventUpdateType: " + rawUpdateType); + wrapper.cancel(); + return; + } + + final Entity entity = entityTracker.getEntityByUid(bossUniqueEntityId); + if (entity == null) { + wrapper.cancel(); + return; + } + wrapper.write(Types.UUID, entity.javaUuid()); // uuid + switch (updateType) { + case Add -> { + if (!entity.hasBossBar()) { + entity.setHasBossBar(true); + wrapper.write(Types.VAR_INT, BossEventOperationType.ADD.ordinal()); // operation + wrapper.write(Types.TAG, TextUtil.stringToNbt(wrapper.user().get(ResourcePacksStorage.class).getTexts().translate(wrapper.read(BedrockTypes.STRING)))); // name + wrapper.write(Types.FLOAT, wrapper.read(BedrockTypes.FLOAT_LE)); // progress + wrapper.read(BedrockTypes.UNSIGNED_SHORT_LE); // darken screen | Does nothing in Bedrock Edition + wrapper.write(Types.VAR_INT, MathUtil.getOrFallback(wrapper.read(BedrockTypes.UNSIGNED_VAR_INT), 0, 5, 0)); // color + wrapper.read(BedrockTypes.UNSIGNED_VAR_INT); // overlay | Does nothing in Bedrock Edition + wrapper.write(Types.VAR_INT, 0); // overlay + wrapper.write(Types.UNSIGNED_BYTE, (short) 0); // flags + } else { + wrapper.cancel(); + } + } + case Remove -> { + entity.setHasBossBar(false); + wrapper.write(Types.VAR_INT, BossEventOperationType.REMOVE.ordinal()); // operation + } + case Update_Percent -> { + wrapper.write(Types.VAR_INT, BossEventOperationType.UPDATE_PROGRESS.ordinal()); // operation + wrapper.write(Types.FLOAT, wrapper.read(BedrockTypes.FLOAT_LE)); // progress + } + case Update_Name -> { + wrapper.write(Types.VAR_INT, BossEventOperationType.UPDATE_NAME.ordinal()); // operation + wrapper.write(Types.TAG, TextUtil.stringToNbt(wrapper.user().get(ResourcePacksStorage.class).getTexts().translate(wrapper.read(BedrockTypes.STRING)))); // name + } + case Update_Properties -> { + wrapper.write(Types.VAR_INT, BossEventOperationType.UPDATE_STYLE.ordinal()); // operation + wrapper.read(BedrockTypes.UNSIGNED_SHORT_LE); // darken screen | Does nothing in Bedrock Edition + wrapper.write(Types.VAR_INT, MathUtil.getOrFallback(wrapper.read(BedrockTypes.UNSIGNED_VAR_INT), 0, 5, 0)); // color + wrapper.read(BedrockTypes.UNSIGNED_VAR_INT); // overlay | Does nothing in Bedrock Edition + wrapper.write(Types.VAR_INT, 0); // overlay + } + case Update_Style -> { + wrapper.write(Types.VAR_INT, BossEventOperationType.UPDATE_STYLE.ordinal()); // operation + wrapper.write(Types.VAR_INT, MathUtil.getOrFallback(wrapper.read(BedrockTypes.UNSIGNED_VAR_INT), 0, 5, 0)); // color + wrapper.read(BedrockTypes.UNSIGNED_VAR_INT); // overlay | Does nothing in Bedrock Edition + wrapper.write(Types.VAR_INT, 0); // overlay + } + case PlayerAdded, PlayerRemoved, Query -> wrapper.cancel(); + default -> throw new IllegalStateException("Unhandled BossEventUpdateType: " + updateType); + } + }); protocol.registerClientbound(ClientboundBedrockPackets.DEATH_INFO, null, wrapper -> { wrapper.cancel(); final GameSessionStorage gameSession = wrapper.user().get(GameSessionStorage.class); diff --git a/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java b/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java index af74953..76dcfcf 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java @@ -162,9 +162,7 @@ public void tick() { public void prepareForRespawn() { for (Entity entity : this.entities.values()) { - if (entity instanceof PlayerEntity player) { - player.deleteTeam(); - } + entity.remove(); } }