From b0a8b9219a75fc7f202e3777507f16db0fd6ccb3 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 22 Apr 2020 23:40:49 -0500 Subject: [PATCH] Add effects support and block break particles/place sounds Co-authored-by: RednedEpic --- .../network/session/GeyserSession.java | 7 + .../bedrock/BedrockActionTranslator.java | 10 ++ ...BedrockInventoryTransactionTranslator.java | 27 ++++ .../network/translators/effect/Effect.java | 43 ++++++ .../player/JavaPlayerActionAckTranslator.java | 11 +- .../java/world/JavaBlockChangeTranslator.java | 22 ++++ .../java/world/JavaPlayEffectTranslator.java | 122 ++++++++++++++++++ .../world/JavaSpawnParticleTranslator.java | 105 +++++++++++++++ .../geysermc/connector/utils/EffectUtils.java | 106 +++++++++++++++ .../org/geysermc/connector/utils/Toolbox.java | 4 +- connector/src/main/resources/mappings | 2 +- 11 files changed, 455 insertions(+), 4 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/effect/Effect.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/EffectUtils.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 12ae39bf..7aaed75f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsExcepti import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.packetlib.Client; @@ -126,6 +127,12 @@ public class GeyserSession implements CommandSender { @Setter private boolean jumping; + @Setter + private BlockState breakingBlock; + + @Setter + private Vector3i lastBlockPlacePosition; + @Setter private boolean switchingDimension = false; private boolean manyDimPackets = false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java index 7ab71389..43e0a570 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java @@ -27,6 +27,10 @@ package org.geysermc.connector.network.translators.bedrock; import java.util.concurrent.TimeUnit; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -41,6 +45,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; +import org.geysermc.connector.network.translators.block.BlockTranslator; @Translator(packet = PlayerActionPacket.class) public class BedrockActionTranslator extends PacketTranslator { @@ -107,6 +112,11 @@ public class BedrockActionTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlayEffectPacket packet, GeyserSession session) { + LevelEventPacket effect = new LevelEventPacket(); + // Some things here are particles, others are not + if (packet.getEffect() instanceof ParticleEffect) { + ParticleEffect particleEffect = (ParticleEffect) packet.getEffect(); + Effect geyserEffect = EffectUtils.EFFECTS.get(particleEffect.name()); + if (geyserEffect != null) { + String name = geyserEffect.getBedrockName(); + effect.setType(LevelEventType.valueOf(name)); + } else { + switch (particleEffect) { + // TODO: BREAK_SPLASH_POTION has additional data + // TODO: Block break doesn't work when you're the player. + case BONEMEAL_GROW: + effect.setType(LevelEventType.BONEMEAL); + BonemealGrowEffectData growEffectData = (BonemealGrowEffectData) packet.getData(); + effect.setData(growEffectData.getParticleCount()); + break; + //TODO: Block break particles when under fire + case BREAK_BLOCK: + effect.setType(LevelEventType.DESTROY); + BreakBlockEffectData breakBlockEffectData = (BreakBlockEffectData) packet.getData(); + effect.setData(BlockTranslator.getBedrockBlockId(breakBlockEffectData.getBlockState())); + break; + // TODO: Check these three + case EXPLOSION: + effect.setType(LevelEventType.PARTICLE_EXPLODE); + break; + case MOB_SPAWN: + effect.setType(LevelEventType.ENTITY_SPAWN); + break; + // Done with a dispenser + case SMOKE: + // Might need to be SHOOT + effect.setType(LevelEventType.PARTICLE_SMOKE); + break; + default: + GeyserConnector.getInstance().getLogger().debug("No effect handling for particle effect: " + packet.getEffect()); + } + } + effect.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + session.getUpstream().sendPacket(effect); + } else if (packet.getEffect() instanceof SoundEffect) { + SoundEffect soundEffect = (SoundEffect) packet.getEffect(); + Effect geyserEffect = EffectUtils.EFFECTS.get(soundEffect.name()); + if (geyserEffect != null) { + // Some events are LevelEventTypes, some are SoundEvents. + if (geyserEffect.getType().equals("soundLevel")) { + // TODO: Opening doors also does not work as the player + effect.setType(LevelEventType.valueOf(geyserEffect.getBedrockName())); + } else if (geyserEffect.getType().equals("soundEvent")) { + LevelSoundEvent2Packet soundEvent = new LevelSoundEvent2Packet(); + // Separate case since each RecordEffectData in Java is an individual track in Bedrock + if (geyserEffect.getJavaName().equals("RECORD")) { + RecordEffectData recordEffectData = (RecordEffectData) packet.getData(); + soundEvent.setSound(EffectUtils.RECORDS.get(recordEffectData.getRecordId())); + } else { + soundEvent.setSound(SoundEvent.valueOf(geyserEffect.getBedrockName())); + } + soundEvent.setExtraData(geyserEffect.getData()); + soundEvent.setIdentifier(geyserEffect.getIdentifier()); + soundEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + session.getUpstream().sendPacket(soundEvent); + } + } else { + GeyserConnector.getInstance().getLogger().debug("No effect handling for sound effect: " + packet.getEffect()); + } + } + if (effect.getType() != null) { + effect.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + session.getUpstream().sendPacket(effect); + } + + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java new file mode 100644 index 00000000..4dd62647 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.world.particle.*; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.utils.EffectUtils; + +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerSpawnParticlePacket; +import com.nukkitx.math.vector.Vector3f; + +@Translator(packet = ServerSpawnParticlePacket.class) +public class JavaSpawnParticleTranslator extends PacketTranslator { + + @Override + public void translate(ServerSpawnParticlePacket packet, GeyserSession session) { + LevelEventPacket particle = new LevelEventPacket(); + switch (packet.getParticle().getType()) { + case BLOCK: + particle.setType(LevelEventType.DESTROY); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + particle.setData(BlockTranslator.getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState())); + session.getUpstream().sendPacket(particle); + break; + case FALLING_DUST: + //In fact, FallingDustParticle should have data like DustParticle, + //but in MCProtocol, its data is BlockState(1). + particle.setType(LevelEventType.PARTICLE_FALLING_DUST); + particle.setData(BlockTranslator.getBedrockBlockId(((FallingDustParticleData)packet.getParticle().getData()).getBlockState())); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.getUpstream().sendPacket(particle); + break; + case ITEM: + ItemStack javaItem = ((ItemParticleData)packet.getParticle().getData()).getItemStack(); + ItemData bedrockItem = Translators.getItemTranslator().translateToBedrock(session, javaItem); + int id = bedrockItem.getId(); + short damage = bedrockItem.getDamage(); + particle.setType(LevelEventType.PARTICLE_ITEM_BREAK); + particle.setData(id << 16 | damage); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.getUpstream().sendPacket(particle); + break; + case DUST: + DustParticleData data = (DustParticleData)packet.getParticle().getData(); + int r = (int) (data.getRed()*255); + int g = (int) (data.getGreen()*255); + int b = (int) (data.getBlue()*255); + particle.setType(LevelEventType.PARTICLE_FALLING_DUST); + particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.getUpstream().sendPacket(particle); + break; + default: + LevelEventType typeParticle = EffectUtils.getParticleLevelEventType(packet.getParticle().getType()); + if (typeParticle != null) { + particle.setType(typeParticle); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.getUpstream().sendPacket(particle); + } else { + String stringParticle = EffectUtils.getParticleString(packet.getParticle().getType()); + if (stringParticle != null) { + SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); + stringPacket.setIdentifier(stringParticle); + stringPacket.setDimensionId(session.getPlayerEntity().getDimension()); + stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.getUpstream().sendPacket(stringPacket); + } + } + break; + } + } + +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/utils/EffectUtils.java b/connector/src/main/java/org/geysermc/connector/utils/EffectUtils.java new file mode 100644 index 00000000..d5a78e22 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/EffectUtils.java @@ -0,0 +1,106 @@ +package org.geysermc.connector.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.world.particle.ParticleType; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.SoundEvent; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +import lombok.NonNull; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.translators.effect.Effect; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class EffectUtils { + + public static final Map EFFECTS = new HashMap<>(); + public static final Int2ObjectMap RECORDS = new Int2ObjectOpenHashMap<>(); + + private static Map particleTypeMap = new HashMap<>(); + private static Map particleStringMap = new HashMap<>(); + + public static void init() { + // no-op + } + + static { + /* Load particles */ + InputStream particleStream = Toolbox.getResource("mappings/particles.json"); + JsonNode particleEntries; + try { + particleEntries = Toolbox.JSON_MAPPER.readTree(particleStream); + } catch (Exception e) { + throw new AssertionError("Unable to load particle map", e); + } + + Iterator> particlesIterator = particleEntries.fields(); + while (particlesIterator.hasNext()) { + Map.Entry entry = particlesIterator.next(); + try { + setIdentifier(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase())); + } catch (IllegalArgumentException e1) { + try { + setIdentifier(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText()); + GeyserConnector.getInstance().getLogger().debug("Force to map particle " + + entry.getKey() + + "=>" + + entry.getValue().asText() + + ", it will take effect."); + } catch (IllegalArgumentException e2){ + GeyserConnector.getInstance().getLogger().warning("Fail to map particle " + entry.getKey() + "=>" + entry.getValue().asText()); + } + } + } + + /* Load effects */ + InputStream effectsStream = Toolbox.getResource("mappings/effects.json"); + JsonNode effects; + try { + effects = Toolbox.JSON_MAPPER.readTree(effectsStream); + } catch (Exception e) { + throw new AssertionError("Unable to load effects mappings", e); + } + + Iterator> effectsIterator = effects.fields(); + while (effectsIterator.hasNext()) { + Map.Entry entry = effectsIterator.next(); + // Separate records database since they're handled differently between the two versions + if (entry.getValue().has("records")) { + JsonNode records = entry.getValue().get("records"); + Iterator> recordsIterator = records.fields(); + while (recordsIterator.hasNext()) { + Map.Entry recordEntry = recordsIterator.next(); + RECORDS.put(Integer.parseInt(recordEntry.getKey()), SoundEvent.valueOf(recordEntry.getValue().asText())); + } + } + String identifier = (entry.getValue().has("identifier")) ? entry.getValue().get("identifier").asText() : ""; + int data = (entry.getValue().has("data")) ? entry.getValue().get("data").asInt() : -1; + Effect effect = new Effect(entry.getKey(), entry.getValue().get("name").asText(), entry.getValue().get("type").asText(), data, identifier); + EFFECTS.put(entry.getKey(), effect); + } + } + + public static void setIdentifier(ParticleType type, LevelEventType identifier) { + particleTypeMap.put(type, identifier); + } + + public static void setIdentifier(ParticleType type, String identifier) { + particleStringMap.put(type, identifier); + } + + public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { + return particleTypeMap.getOrDefault(type, null); + } + + public static String getParticleString(@NonNull ParticleType type){ + return particleStringMap.getOrDefault(type, null); + } + +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index 24cea580..9dbc51ab 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -128,7 +128,9 @@ public class Toolbox { } itemIndex++; } - + + // Load particle/effect mappings + EffectUtils.init(); // Load sound mappings SoundUtils.init(); // Load the locale data diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 9ecd90c7..b03f5611 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 9ecd90c71a26423a5f824554cce9b4236e544723 +Subproject commit b03f56113199a1a360efc68d2a80b8f706c6f56d