Merge pull request #249 from endevrr/block-break-animations (Closes #153)

Block breaking animations
This commit is contained in:
Redned 2020-03-28 13:58:44 -05:00 committed by GitHub
commit 4a8aeda677
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 379 additions and 7 deletions

View file

@ -48,6 +48,24 @@
<version>8.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-double-maps</artifactId>
<version>8.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-boolean-maps</artifactId>
<version>8.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-object-int-maps</artifactId>
<version>8.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-object-byte-maps</artifactId>

View file

@ -39,6 +39,7 @@ import lombok.Setter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.EntityEffectCache;
import org.geysermc.connector.utils.SkinUtils;
import java.util.UUID;
@ -50,6 +51,7 @@ public class PlayerEntity extends LivingEntity {
private String username;
private long lastSkinUpdate = -1;
private boolean playerList = true;
private final EntityEffectCache effectCache;
public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation);
@ -57,6 +59,7 @@ public class PlayerEntity extends LivingEntity {
profile = gameProfile;
uuid = gameProfile.getId();
username = gameProfile.getName();
effectCache = new EntityEffectCache();
if (geyserId == 1) valid = true;
}

View file

@ -50,6 +50,6 @@ public class PlayerInventory extends Inventory {
}
public ItemStack getItemInHand() {
return items[heldItemSlot];
return items[36 + heldItemSlot];
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.session.cache;
import com.github.steveice10.mc.protocol.data.game.entity.Effect;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter;
public class EntityEffectCache {
@Getter
private final Object2IntMap<Effect> entityEffects = new Object2IntOpenHashMap<>();
public void addEffect(Effect effect, int effectAmplifier) {
if (effect != null) {
entityEffects.putIfAbsent(effect, effectAmplifier + 1);
}
}
public void removeEffect(Effect effect) {
if (entityEffects.containsKey(effect)) {
int effectLevel = entityEffects.getInt(effect);
entityEffects.remove(effect, effectLevel);
}
}
public int getEffectLevel(Effect effect) {
return entityEffects.getOrDefault(effect, 0);
}
}

View file

@ -47,12 +47,15 @@ public class Registry<T> {
BEDROCK.MAP.put(clazz, translator);
}
@SuppressWarnings("unchecked")
public <P extends T> boolean translate(Class<? extends P> clazz, P packet, GeyserSession session) {
if (!session.getUpstream().isClosed() && !session.isClosed()) {
try {
if (MAP.containsKey(clazz)) {
((PacketTranslator<P>) MAP.get(clazz)).translate(packet, session);
return true;
} else {
GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
}
} catch (Throwable ex) {
GeyserConnector.getInstance().getLogger().error("Could not translate packet " + packet.getClass().getSimpleName(), ex);

View file

@ -54,6 +54,13 @@ public class BlockTranslator {
private static final Object2ByteOpenHashMap<BlockState> BED_COLORS = new Object2ByteOpenHashMap<>();
private static final Map<BlockState, String> JAVA_ID_TO_BLOCK_ENTITY_MAP = new HashMap<>();
public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap();
public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap();
public static final Int2ObjectMap<String> JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>();
// For block breaking animation math
public static final List<Integer> JAVA_RUNTIME_WOOL_IDS = new ArrayList<>();
public static final int JAVA_RUNTIME_COBWEB_ID;
private static final int BLOCK_STATE_VERSION = 17760256;
@ -89,6 +96,7 @@ public class BlockTranslator {
int waterRuntimeId = -1;
int javaRuntimeId = -1;
int bedrockRuntimeId = 0;
int cobwebRuntimeId = -1;
Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocks.fields();
while (blocksIterator.hasNext()) {
javaRuntimeId++;
@ -97,6 +105,27 @@ public class BlockTranslator {
BlockState javaBlockState = new BlockState(javaRuntimeId);
CompoundTag blockTag = buildBedrockState(entry.getValue());
// TODO fix this, (no block should have a null hardness)
JsonNode hardnessNode = entry.getValue().get("block_hardness");
if (hardnessNode != null) {
JAVA_RUNTIME_ID_TO_HARDNESS.put(javaRuntimeId, hardnessNode.doubleValue());
}
JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, entry.getValue().get("can_break_with_hand").booleanValue());
JsonNode toolTypeNode = entry.getValue().get("tool_type");
if (toolTypeNode != null) {
JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue());
}
if (javaId.contains("wool")) {
JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId);
}
if (javaId.contains("cobweb")) {
cobwebRuntimeId = javaRuntimeId;
}
JAVA_ID_BLOCK_MAP.put(javaId, javaBlockState);
if (javaId.contains("sign[")) {
@ -142,6 +171,11 @@ public class BlockTranslator {
bedrockRuntimeId++;
}
if (cobwebRuntimeId == -1) {
throw new AssertionError("Unable to find cobwebs in palette");
}
JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId;
if (waterRuntimeId == -1) {
throw new AssertionError("Unable to find water in palette");
}

View file

@ -34,11 +34,11 @@ public class ItemEntry {
public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0);
private String javaIdentifier;
private int javaId;
private final String javaIdentifier;
private final int javaId;
private int bedrockId;
private int bedrockData;
private final int bedrockId;
private final int bedrockData;
@Override
public boolean equals(Object obj) {

View file

@ -0,0 +1,15 @@
package org.geysermc.connector.network.translators.item;
import lombok.Getter;
@Getter
public class ToolItemEntry extends ItemEntry {
private final String toolType;
private final String toolTier;
public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier) {
super(javaIdentifier, javaId, bedrockId, bedrockData);
this.toolType = toolType;
this.toolTier = toolTier;
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.java.entity;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityEffectPacket;
import com.nukkitx.protocol.bedrock.packet.MobEffectPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.utils.EntityUtils;
@ -39,6 +40,7 @@ public class JavaEntityEffectTranslator extends PacketTranslator<ServerEntityEff
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
entity = session.getPlayerEntity();
((PlayerEntity) entity).getEffectCache().addEffect(packet.getEffect(), packet.getAmplifier());
}
if (entity == null)
return;

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.java.entity;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRemoveEffectPacket;
import com.nukkitx.protocol.bedrock.packet.MobEffectPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.utils.EntityUtils;
@ -39,6 +40,7 @@ public class JavaEntityRemoveEffectTranslator extends PacketTranslator<ServerEnt
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
entity = session.getPlayerEntity();
((PlayerEntity) entity).getEffectCache().removeEffect(packet.getEffect());
}
if (entity == null)
return;

View file

@ -25,19 +25,64 @@
package org.geysermc.connector.network.translators.java.entity.player;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerActionAckPacket;
import com.github.steveice10.opennbt.tag.builtin.*;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.TranslatorsInit;
import org.geysermc.connector.network.translators.block.BlockTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.utils.BlockUtils;
import org.geysermc.connector.utils.ChunkUtils;
public class JavaPlayerActionAckTranslator extends PacketTranslator<ServerPlayerActionAckPacket> {
@Override
public void translate(ServerPlayerActionAckPacket packet, GeyserSession session) {
LevelEventPacket levelEvent = new LevelEventPacket();
switch (packet.getAction()) {
case FINISH_DIGGING:
ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition());
break;
case START_DIGGING:
levelEvent.setType(LevelEventType.BLOCK_START_BREAK);
levelEvent.setPosition(Vector3f.from(
packet.getPosition().getX(),
packet.getPosition().getY(),
packet.getPosition().getZ()
));
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState().getId());
PlayerInventory inventory = session.getInventory();
ItemStack item = inventory.getItemInHand();
ItemEntry itemEntry = null;
CompoundTag nbtData = new CompoundTag("");
if (item != null) {
itemEntry = TranslatorsInit.getItemTranslator().getItem(item);
nbtData = item.getNbt();
}
double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState().getId(), itemEntry, nbtData, session.getPlayerEntity()) * 20);
levelEvent.setData((int) (65535 / breakTime));
session.getUpstream().sendPacket(levelEvent);
break;
case CANCEL_DIGGING:
levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK);
levelEvent.setPosition(Vector3f.from(
packet.getPosition().getX(),
packet.getPosition().getY(),
packet.getPosition().getZ()
));
levelEvent.setData(0);
session.getUpstream().sendPacket(levelEvent);
break;
}
}
}

View file

@ -0,0 +1,128 @@
/*
* 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.utils;
import com.github.steveice10.mc.protocol.data.game.entity.Effect;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.translators.block.BlockTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ToolItemEntry;
public class BlockUtils {
private static boolean correctTool(String blockToolType, String itemToolType) {
return (blockToolType.equals("sword") && itemToolType.equals("sword")) ||
(blockToolType.equals("shovel") && itemToolType.equals("shovel")) ||
(blockToolType.equals("pickaxe") && itemToolType.equals("pickaxe")) ||
(blockToolType.equals("axe") && itemToolType.equals("axe")) ||
(blockToolType.equals("shears") && itemToolType.equals("shears"));
}
private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isWoolBlock) {
if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0;
if (toolType.equals("")) return 1.0;
switch (toolTier) {
case "wooden":
return 2.0;
case "stone":
return 4.0;
case "iron":
return 6.0;
case "diamond":
return 8.0;
case "golden":
return 12.0;
default:
return 1.0;
}
}
//http://minecraft.gamepedia.com/Breaking
private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool,
String toolType, boolean isWoolBlock, boolean isCobweb, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel
/*boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround*/) {
double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness;
double speed = 1.0 / baseTime;
if (correctTool) {
speed *= toolBreakTimeBonus(toolType, toolTier, isWoolBlock);
speed += toolEfficiencyLevel == 0 ? 0 : toolEfficiencyLevel * toolEfficiencyLevel + 1;
} else if (toolType.equals("sword")) {
speed*= (isCobweb ? 15.0 : 1.5);
}
speed *= 1.0 + (0.2 * hasteLevel);
switch (miningFatigueLevel) {
case 0:
break;
case 1:
speed -= (speed * 0.7);
break;
case 2:
speed -= (speed * 0.91);
break;
case 3:
speed -= (speed * 0.9973);
break;
default:
speed -= (speed * 0.99919);
break;
}
//if (insideOfWaterWithoutAquaAffinity) speed *= 0.2;
//if (outOfWaterButNotOnGround) speed *= 0.2;
// else if insideWaterAndNotOnGround speed *= 0.2;
return 1.0 / speed;
}
public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, PlayerEntity player) {
boolean isWoolBlock = BlockTranslator.JAVA_RUNTIME_WOOL_IDS.contains(blockId);
boolean isCobweb = blockId == BlockTranslator.JAVA_RUNTIME_COBWEB_ID;
String blockToolType = BlockTranslator.JAVA_RUNTIME_ID_TO_TOOL_TYPE.getOrDefault(blockId, "");
boolean canHarvestWithHand = BlockTranslator.JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.get(blockId);
String toolType = "";
String toolTier = "";
boolean correctTool = false;
if (item instanceof ToolItemEntry) {
ToolItemEntry toolItem = (ToolItemEntry) item;
toolType = toolItem.getToolType();
toolTier = toolItem.getToolTier();
correctTool = correctTool(blockToolType, toolType);
}
int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency");
int hasteLevel = player.getEffectCache().getEffectLevel(Effect.FASTER_DIG);
int miningFatigueLevel = player.getEffectCache().getEffectLevel(Effect.SLOWER_DIG);
// TODO implement these checks and material check if possible
//boolean insideOfWaterWithoutAquaAffinity = player.isInsideOfWater() &&
// Optional.ofNullable(player.getInventory().getHelmet().getEnchantment(Enchantment.ID_WATER_WORKER))
// .map(Enchantment::getLevel).map(l -> l >= 1).orElse(false);
//boolean outOfWaterButNotOnGround = (!player.isInsideOfWater()) && (!player.isOnGround());
return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel);
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.utils;
import com.github.steveice10.opennbt.tag.builtin.*;
public class ItemUtils {
public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) {
ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments"));
if (enchantments != null) {
int enchantmentLevel = 0;
for (Tag tag : enchantments) {
CompoundTag enchantment = (CompoundTag) tag;
StringTag enchantId = enchantment.get("id");
if (enchantId.getValue().equals(enchantmentId)) {
enchantmentLevel = (int) ((ShortTag) enchantment.get("lvl")).getValue();
}
}
return enchantmentLevel;
}
return 0;
}
}

View file

@ -40,6 +40,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ToolItemEntry;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -103,8 +104,28 @@ public class Toolbox {
Iterator<Map.Entry<String, JsonNode>> iterator = items.fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> entry = iterator.next();
ITEM_ENTRIES.put(itemIndex, new ItemEntry(entry.getKey(), itemIndex,
entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue()));
if (entry.getValue().has("tool_type")) {
if (entry.getValue().has("tool_tier")) {
ITEM_ENTRIES.put(itemIndex, new ToolItemEntry(
entry.getKey(), itemIndex,
entry.getValue().get("bedrock_id").intValue(),
entry.getValue().get("bedrock_data").intValue(),
entry.getValue().get("tool_type").textValue(),
entry.getValue().get("tool_tier").textValue()));
} else {
ITEM_ENTRIES.put(itemIndex, new ToolItemEntry(
entry.getKey(), itemIndex,
entry.getValue().get("bedrock_id").intValue(),
entry.getValue().get("bedrock_data").intValue(),
entry.getValue().get("tool_type").textValue(),
""));
}
} else {
ITEM_ENTRIES.put(itemIndex, new ItemEntry(
entry.getKey(), itemIndex,
entry.getValue().get("bedrock_id").intValue(),
entry.getValue().get("bedrock_data").intValue()));
}
itemIndex++;
}