Skip to content

Commit

Permalink
Implemented entity interaction translation
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphiMC committed Sep 15, 2024
1 parent 5093d40 commit 85a98c6
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 23 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ If you want to talk about ViaBedrock or learn more about it you can join my [Dis
- [x] Biomes
- [x] Player spawning
- [x] Entity spawning
- [ ] Entity interactions
- [x] Entity interactions
- [ ] Entity metadata
- [x] Entity attributes
- [ ] Entity mounting
Expand All @@ -43,7 +43,7 @@ If you want to talk about ViaBedrock or learn more about it you can join my [Dis
- [x] Bossbar
- [x] Player list
- [x] Command suggestions
- [x] Sounds (No mob ambient sounds yet)
- [x] Sounds (No mob sounds yet)
- [x] Particles
- [x] Player Skins (Requires [BedrockSkinUtility](https://github.com/Camotoy/BedrockSkinUtility) mod)
- [x] Basic resource pack conversion (Contributions welcome)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ public byte javaWindowId() {
return (byte) ContainerID.CONTAINER_ID_INVENTORY.getValue();
}

public byte getSelectedHotbarSlot() {
return this.selectedHotbarSlot;
}

public BedrockItem getSelectedHotbarItem() {
return this.getItem(this.selectedHotbarSlot);
}

public void sendSelectedHotbarSlotToClient() {
final PacketWrapper setCarriedItem = PacketWrapper.create(ClientboundPackets1_21.SET_CARRIED_ITEM, this.user);
setCarriedItem.write(Types.BYTE, this.selectedHotbarSlot);
Expand Down Expand Up @@ -122,7 +130,7 @@ private void onSelectedHotbarSlotChanged(final BedrockItem oldItem, final Bedroc
final PacketWrapper interact = PacketWrapper.create(ServerboundBedrockPackets.INTERACT, this.user);
interact.write(Types.BYTE, (byte) InteractPacket_Action.InteractUpdate.getValue()); // action
interact.write(BedrockTypes.UNSIGNED_VAR_LONG, 0L); // target runtime entity id
interact.write(BedrockTypes.POSITION_3F, new Position3f(0F, 0F, 0F)); // mouse position
interact.write(BedrockTypes.POSITION_3F, Position3f.ZERO); // mouse position
interact.sendToServer(BedrockProtocol.class);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viabedrock.protocol.data.enums.java;

public enum InteractActionType {

INTERACT,
ATTACK,
INTERACT_AT,

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21;
import com.viaversion.viaversion.util.Pair;
import net.raphimc.viabedrock.ViaBedrock;
import net.raphimc.viabedrock.api.model.container.player.InventoryContainer;
import net.raphimc.viabedrock.api.model.entity.ClientPlayerEntity;
import net.raphimc.viabedrock.api.model.entity.Entity;
import net.raphimc.viabedrock.api.util.BitSets;
import net.raphimc.viabedrock.api.util.PacketFactory;
import net.raphimc.viabedrock.protocol.BedrockProtocol;
Expand All @@ -37,6 +39,7 @@
import net.raphimc.viabedrock.protocol.data.enums.java.*;
import net.raphimc.viabedrock.protocol.model.Position3f;
import net.raphimc.viabedrock.protocol.rewriter.GameTypeRewriter;
import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter;
import net.raphimc.viabedrock.protocol.storage.*;
import net.raphimc.viabedrock.protocol.types.BedrockTypes;

Expand Down Expand Up @@ -215,7 +218,7 @@ protected void register() {

switch (action) {
case PERFORM_RESPAWN -> {
wrapper.write(BedrockTypes.POSITION_3F, new Position3f(0F, 0F, 0F)); // position
wrapper.write(BedrockTypes.POSITION_3F, Position3f.ZERO); // position
wrapper.write(Types.BYTE, (byte) PlayerRespawnState.ClientReadyToSpawn.getValue()); // state
wrapper.write(BedrockTypes.UNSIGNED_VAR_LONG, clientPlayer.runtimeId()); // runtime entity id
}
Expand Down Expand Up @@ -355,6 +358,54 @@ protected void register() {
default -> throw new IllegalStateException("Unhandled PlayerActionAction: " + action);
}
});
protocol.registerServerbound(ServerboundPackets1_20_5.INTERACT, ServerboundBedrockPackets.INVENTORY_TRANSACTION, wrapper -> {
final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class);
final InventoryContainer inventoryContainer = wrapper.user().get(InventoryTracker.class).getInventoryContainer();
final int entityId = wrapper.read(Types.VAR_INT); // entity id
final InteractActionType action = InteractActionType.values()[wrapper.read(Types.VAR_INT)]; // action
final Entity entity = entityTracker.getEntityByJid(entityId);
if (entity == null) {
wrapper.cancel();
return;
}

wrapper.write(BedrockTypes.VAR_INT, 0); // legacy request id
wrapper.write(BedrockTypes.UNSIGNED_VAR_INT, ComplexInventoryTransaction_Type.ItemUseOnEntityTransaction.getValue()); // transaction type
wrapper.write(BedrockTypes.UNSIGNED_VAR_INT, 0); // actions count
wrapper.write(BedrockTypes.UNSIGNED_VAR_LONG, entity.runtimeId()); // runtime entity id
wrapper.write(BedrockTypes.UNSIGNED_VAR_INT, (switch (action) {
case INTERACT, INTERACT_AT -> ItemUseOnActorInventoryTransaction_ActionType.Interact;
case ATTACK -> ItemUseOnActorInventoryTransaction_ActionType.Attack;
default -> throw new IllegalStateException("Unhandled InteractActionType: " + action);
}).getValue()); // action type
wrapper.write(BedrockTypes.VAR_INT, (int) inventoryContainer.getSelectedHotbarSlot()); // hotbar slot
wrapper.write(wrapper.user().get(ItemRewriter.class).itemType(), inventoryContainer.getSelectedHotbarItem()); // hand item
wrapper.write(BedrockTypes.POSITION_3F, entityTracker.getClientPlayer().position()); // player position

switch (action) {
case INTERACT -> wrapper.cancel();
case ATTACK -> {
wrapper.read(Types.BOOLEAN); // secondary action
wrapper.write(BedrockTypes.POSITION_3F, Position3f.ZERO); // click position

entityTracker.getClientPlayer().sendSwingPacketToServer();
entityTracker.getClientPlayer().cancelNextSwingPacket();
}
case INTERACT_AT -> {
final float x = wrapper.read(Types.FLOAT); // x
final float y = wrapper.read(Types.FLOAT); // y
final float z = wrapper.read(Types.FLOAT); // z
final InteractionHand hand = InteractionHand.values()[wrapper.read(Types.VAR_INT)]; // hand
if (hand != InteractionHand.MAIN_HAND) {
wrapper.cancel();
return;
}
wrapper.read(Types.BOOLEAN); // secondary action
wrapper.write(BedrockTypes.POSITION_3F, entity.position().add(x, y, z)); // click position
}
default -> throw new IllegalStateException("Unhandled InteractActionType: " + action);
}
});
protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_STATUS_ONLY, ServerboundBedrockPackets.MOVE_PLAYER, wrapper -> {
final ClientPlayerEntity clientPlayer = wrapper.user().get(EntityTracker.class).getClientPlayer();
clientPlayer.updatePlayerPosition(wrapper, wrapper.read(Types.BOOLEAN));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,35 +394,50 @@ public static void register(final BedrockProtocol protocol) {
setEntityData.write(Types1_21.ENTITY_DATA_LIST, Lists.newArrayList(new EntityData(entity.getJavaEntityDataIndex("PAINTING_VARIANT"), Types1_21.ENTITY_DATA_TYPES.paintingVariantType, paintingHolder))); // entity data
setEntityData.send(BedrockProtocol.class);
});
protocol.registerClientbound(ClientboundBedrockPackets.ENTITY_EVENT, null, wrapper -> {
wrapper.cancel();
protocol.registerClientbound(ClientboundBedrockPackets.ENTITY_EVENT, ClientboundPackets1_21.ENTITY_EVENT, wrapper -> {
final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class);
final GameSessionStorage gameSession = wrapper.user().get(GameSessionStorage.class);

final long runtimeEntityId = wrapper.read(BedrockTypes.UNSIGNED_VAR_LONG); // runtime entity id
final byte rawEvent = wrapper.read(Types.BYTE); // event
final ActorEvent event = ActorEvent.getByValue(rawEvent); // event
if (event == null) {
wrapper.cancel();
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Unknown ActorEvent: " + rawEvent);
return;
}
final int data = wrapper.read(BedrockTypes.VAR_INT); // data

final Entity entity = entityTracker.getEntityByRid(runtimeEntityId);
if (entity == null) {
wrapper.cancel();
return;
}
switch (event) {
case HURT -> {
final CompoundTag damageTypeRegistry = gameSession.getJavaRegistries().getCompoundTag("minecraft:damage_type");
wrapper.setPacketType(ClientboundPackets1_21.DAMAGE_EVENT);
wrapper.write(Types.VAR_INT, entity.javaId()); // entity id
wrapper.write(Types.VAR_INT, RegistryUtil.getRegistryIndex(damageTypeRegistry, damageTypeRegistry.getCompoundTag("minecraft:generic"))); // source type
wrapper.write(Types.VAR_INT, 0); // source cause id
wrapper.write(Types.VAR_INT, 0); // source direct id
wrapper.write(Types.BOOLEAN, false); // has source position
}
case DEATH -> {
if (runtimeEntityId == entityTracker.getClientPlayer().runtimeId()) {
entityTracker.getClientPlayer().setHealth(0F);
entityTracker.getClientPlayer().sendAttribute("minecraft:health");

if (gameSession.getDeathMessage() != null && entityTracker.getClientPlayer().isDead()) {
final PacketWrapper playerCombatKill = PacketWrapper.create(ClientboundPackets1_21.PLAYER_COMBAT_KILL, wrapper.user());
playerCombatKill.write(Types.VAR_INT, entityTracker.getClientPlayer().javaId()); // entity id
playerCombatKill.write(Types.TAG, TextUtil.textComponentToNbt(gameSession.getDeathMessage())); // message
playerCombatKill.send(BedrockProtocol.class);
}
wrapper.cancel();
if (entity instanceof LivingEntity livingEntity) {
livingEntity.setHealth(0F);
livingEntity.sendAttribute("minecraft:health");
}
if (entity == entityTracker.getClientPlayer() && entityTracker.getClientPlayer().isDead() && gameSession.getDeathMessage() != null) {
final PacketWrapper playerCombatKill = PacketWrapper.create(ClientboundPackets1_21.PLAYER_COMBAT_KILL, wrapper.user());
playerCombatKill.write(Types.VAR_INT, entityTracker.getClientPlayer().javaId()); // entity id
playerCombatKill.write(Types.TAG, TextUtil.textComponentToNbt(gameSession.getDeathMessage())); // message
playerCombatKill.send(BedrockProtocol.class);
}
}
default -> {
wrapper.cancel();
// TODO: Handle remaining events
// throw new IllegalStateException("Unhandled ActorEvent: " + event);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public static void register(final BedrockProtocol protocol) {
final PacketWrapper interact = PacketWrapper.create(ServerboundBedrockPackets.INTERACT, wrapper.user());
interact.write(Types.BYTE, (byte) InteractPacket_Action.InteractUpdate.getValue()); // action
interact.write(BedrockTypes.UNSIGNED_VAR_LONG, 0L); // target runtime entity id
interact.write(BedrockTypes.POSITION_3F, new Position3f(0F, 0F, 0F)); // mouse position
interact.write(BedrockTypes.POSITION_3F, Position3f.ZERO); // mouse position
interact.sendToServer(BedrockProtocol.class);

final PacketWrapper emoteList = PacketWrapper.create(ServerboundBedrockPackets.EMOTE_LIST, wrapper.user());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public class EntityTracker extends StoredObject {
private final AtomicInteger ID_COUNTER = new AtomicInteger(1);

private ClientPlayerEntity clientPlayerEntity = null;
private final Map<Long, Long> runtimeIdToUniqueId = new HashMap<>();
private final Map<Long, Entity> entities = new HashMap<>();
private final Map<Long, Long> runtimeIdToUniqueId = new HashMap<>();
private final Map<Integer, Long> javaIdToUniqueId = new HashMap<>();
private final Map<BlockPosition, Integer> itemFrames = new HashMap<>();

public EntityTracker(final UserConnection user) {
Expand Down Expand Up @@ -69,16 +70,19 @@ public <T extends Entity> T addEntity(final T entity, final boolean updateTeam)
this.clientPlayerEntity = (ClientPlayerEntity) entity;
}

if (this.runtimeIdToUniqueId.putIfAbsent(entity.runtimeId(), entity.uniqueId()) != null) {
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Duplicate runtime entity ID: " + entity.runtimeId());
}
final Entity prevEntity = this.entities.put(entity.uniqueId(), entity);
if (prevEntity != null) {
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Duplicate unique entity ID: " + entity.uniqueId());
this.removeEntity(prevEntity);
final PacketWrapper removeEntities = PacketWrapper.create(ClientboundPackets1_21.REMOVE_ENTITIES, this.user());
removeEntities.write(Types.VAR_INT_ARRAY_PRIMITIVE, new int[]{prevEntity.javaId()}); // entity ids
removeEntities.send(BedrockProtocol.class);
prevEntity.remove();
}
if (this.javaIdToUniqueId.put(entity.javaId(), entity.uniqueId()) != null) {
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Duplicate Java entity ID: " + entity.javaId());
}
if (this.runtimeIdToUniqueId.putIfAbsent(entity.runtimeId(), entity.uniqueId()) != null) {
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Duplicate runtime entity ID: " + entity.runtimeId());
}

if (updateTeam && entity instanceof PlayerEntity player) {
Expand All @@ -93,8 +97,9 @@ public void removeEntity(final Entity entity) {
throw new IllegalArgumentException("Cannot remove client player entity");
}

this.runtimeIdToUniqueId.remove(entity.runtimeId());
this.entities.remove(entity.uniqueId());
this.runtimeIdToUniqueId.remove(entity.runtimeId());
this.javaIdToUniqueId.remove(entity.javaId());
entity.remove();
}

Expand Down Expand Up @@ -174,6 +179,10 @@ public Entity getEntityByUid(final long uniqueId) {
return this.entities.get(uniqueId);
}

public Entity getEntityByJid(final int javaId) {
return this.entities.get(this.javaIdToUniqueId.get(javaId));
}

public ClientPlayerEntity getClientPlayer() {
return this.clientPlayerEntity;
}
Expand Down

0 comments on commit 85a98c6

Please sign in to comment.