1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2026-01-06 15:41:50 +00:00

Implement shapeless/complex recipes; fix crawling

This commit is contained in:
Camotoy
2024-10-26 23:48:31 -04:00
parent c9eeed905b
commit 198eeac4c2
8 changed files with 134 additions and 109 deletions

View File

@@ -45,6 +45,7 @@ import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.scoreboard.Team;
import org.geysermc.geyser.session.GeyserSession;

View File

@@ -41,6 +41,7 @@ import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.NetworkSettingsPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.cloudburstmc.protocol.bedrock.packet.RequestNetworkSettingsPacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkRequestPacket;
@@ -64,6 +65,7 @@ import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.geysermc.geyser.util.MathUtils;
@@ -94,6 +96,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
}
private PacketSignal translateAndDefault(BedrockPacket packet) {
if (packet instanceof PlayerAuthInputPacket) {
//System.out.println(packet);
} else {
System.out.println(ChatColor.toANSI(ChatColor.GREEN) + packet + ChatColor.ANSI_RESET);
}
Registries.BEDROCK_PACKET_TRANSLATORS.translate(packet.getClass(), packet, session);
return PacketSignal.HANDLED; // PacketSignal.UNHANDLED will log a WARN publicly
}

View File

@@ -1371,14 +1371,28 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
}
public void setSwimming(boolean swimming) {
if (swimming) {
if (!swimming && playerEntity.getFlag(EntityFlag.CRAWLING)) {
// Do not update bounding box.
playerEntity.setFlag(EntityFlag.SWIMMING, false);
playerEntity.updateBedrockMetadata();
return;
}
toggleSwimmingPose(swimming, EntityFlag.SWIMMING);
}
public void setCrawling(boolean crawling) {
toggleSwimmingPose(crawling, EntityFlag.CRAWLING);
}
private void toggleSwimmingPose(boolean crawling, EntityFlag flag) {
if (crawling) {
this.pose = Pose.SWIMMING;
playerEntity.setBoundingBoxHeight(0.6f);
} else {
this.pose = Pose.STANDING;
playerEntity.setBoundingBoxHeight(playerEntity.getDefinition().height());
}
playerEntity.setFlag(EntityFlag.SWIMMING, swimming);
playerEntity.setFlag(flag, crawling);
playerEntity.updateBedrockMetadata();
}

View File

@@ -44,7 +44,6 @@ public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
return;
}
System.out.println("wewewewewewewewewewewe");
if (packet.getAction() == AnimatePacket.Action.SWING_ARM) {
session.armSwingPending();
// Delay so entity damage can be processed first

View File

@@ -31,14 +31,10 @@ import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block;
@@ -49,7 +45,6 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.translator.item.CustomItemTranslator;
import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@@ -57,24 +52,21 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractActio
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import java.util.List;
final class BedrockBlockActions {
static void translate(GeyserSession session, List<PlayerBlockActionData> playerActions) {
SessionPlayerEntity entity = session.getPlayerEntity();
// Send book update before any player action
session.getBookEditCache().checkForSend();
for (PlayerBlockActionData blockActionData : playerActions) {
handle(session, entity, blockActionData);
handle(session, blockActionData);
}
}
private static void handle(GeyserSession session, SessionPlayerEntity entity, PlayerBlockActionData blockActionData) {
private static void handle(GeyserSession session, PlayerBlockActionData blockActionData) {
PlayerActionType action = blockActionData.getAction();
Vector3i vector = blockActionData.getBlockPosition();
int blockFace = blockActionData.getFace();
@@ -198,34 +190,6 @@ final class BedrockBlockActions {
// Handled in BedrockInventoryTransactionTranslator
case STOP_BREAK -> {
}
case DIMENSION_CHANGE_SUCCESS -> {
//sometimes the client doesn't feel like loading
PlayStatusPacket spawnPacket = new PlayStatusPacket();
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendUpstreamPacket(spawnPacket);
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
session.sendUpstreamPacket(attributesPacket);
}
case MISSED_SWING -> {
// Java edition sends a cooldown when hitting air.
// Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this.
CooldownUtils.sendCooldown(session);
// TODO Re-evaluate after pre-1.20.10 is no longer supported?
if (session.getArmAnimationTicks() == -1) {
session.sendDownstreamGamePacket(new ServerboundSwingPacket(Hand.MAIN_HAND));
session.activateArmAnimationTicking();
// Send packet to Bedrock so it knows
AnimatePacket animatePacket = new AnimatePacket();
animatePacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
session.sendUpstreamPacket(animatePacket);
}
}
}
}

View File

@@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.type.Entity;
@@ -103,6 +104,18 @@ public class BedrockPlayerActionTranslator extends PacketTranslator<PlayerAction
session.sendDownstreamGamePacket(interactPacket);
}
}
case DIMENSION_CHANGE_SUCCESS -> {
SessionPlayerEntity entity = session.getPlayerEntity();
// Sometimes the client doesn't feel like loading
PlayStatusPacket spawnPacket = new PlayStatusPacket();
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendUpstreamPacket(spawnPacket);
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
session.sendUpstreamPacket(attributesPacket);
}
}
}
}

View File

@@ -49,6 +49,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.protocol.bedrock.BedrockInventoryTransactionTranslator;
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
@@ -65,18 +66,23 @@ import java.util.Set;
@Translator(packet = PlayerAuthInputPacket.class)
public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<PlayerAuthInputPacket> {
private Set<PlayerAuthInputData> data = Set.of();
@Override
public void translate(GeyserSession session, PlayerAuthInputPacket packet) {
if (!data.equals(packet.getInputData())) {
System.out.println(data);
this.data = packet.getInputData();
}
SessionPlayerEntity entity = session.getPlayerEntity();
boolean wasJumping = session.getInputCache().wasJumping();
session.getInputCache().processInputs(packet);
processVehicleInput(session, packet, wasJumping);
BedrockMovePlayerTranslator.translate(session, packet);
processVehicleInput(session, packet, wasJumping);
Set<PlayerAuthInputData> inputData = packet.getInputData();
for (PlayerAuthInputData input : inputData) {
switch (input) {
@@ -110,6 +116,8 @@ public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<Pla
}
case START_SWIMMING -> session.setSwimming(true);
case STOP_SWIMMING -> session.setSwimming(false);
case START_CRAWLING -> session.setCrawling(true);
case STOP_CRAWLING -> session.setCrawling(false);
case START_FLYING -> { // Since 1.20.30
if (session.isCanFly()) {
if (session.getGameMode() == GameMode.SPECTATOR) {
@@ -151,6 +159,7 @@ public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<Pla
sendPlayerGlideToggle(session, entity);
}
case STOP_GLIDING -> sendPlayerGlideToggle(session, entity);
case MISSED_SWING -> CooldownUtils.sendCooldown(session); // Java edition sends a cooldown when hitting air.
}
}
if (entity.getVehicle() instanceof BoatEntity) {
@@ -249,7 +258,7 @@ public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<Pla
if (currentJumpingTicks < 0) {
session.getInputCache().setJumpingTicks(++currentJumpingTicks);
if (currentJumpingTicks == 0) {
session.getPlayerEntity().setVehicleJumpStrength(0);
session.getInputCache().setJumpScale(0);
}
}

View File

@@ -33,6 +33,7 @@ import net.kyori.adventure.key.Key;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.RecipeUnlockingRequirement;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData;
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor;
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
@@ -81,8 +82,6 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
int netId = session.getLastRecipeNetId().get();
Int2ObjectMap<List<String>> javaToBedrockRecipeIds = session.getJavaToBedrockRecipeIds();
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
// Check if we should set cleanRecipes here or not.
UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
recipesPacket.setAction(packet.isReplace() ? UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED : UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);
@@ -94,78 +93,42 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
switch (display.getType()) {
case CRAFTING_SHAPED -> {
ShapedCraftingRecipeDisplay shapedRecipe = (ShapedCraftingRecipeDisplay) display;
Pair<Item, ItemData> pair = translateToOutput(session, shapedRecipe.result());
if (pair == null || !pair.right().isValid()) {
// Likely modded item Bedrock will complain about
var bedrockRecipes = combinations(session, display, shapedRecipe.ingredients());
if (bedrockRecipes == null) {
continue;
}
ItemData output = pair.right();
if (!(pair.left() instanceof BedrockRequiresTagItem)) {
// Strip NBT - tools won't appear in the recipe book otherwise
output = output.toBuilder().tag(null).build();
List<String> bedrockRecipeIds = new ArrayList<>();
ItemData output = bedrockRecipes.right();
List<List<ItemDescriptorWithCount>> left = bedrockRecipes.left();
for (int i = 0; i < left.size(); i++) {
List<ItemDescriptorWithCount> inputs = left.get(i);
String recipeId = contents.id() + "_" + i;
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
shapedRecipe.width(), shapedRecipe.height(), inputs,
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
recipesPacket.getUnlockedRecipes().add(recipeId);
bedrockRecipeIds.add(recipeId);
}
boolean empty = true;
boolean complexInputs = false;
List<List<ItemDescriptorWithCount>> inputs = new ArrayList<>(shapedRecipe.ingredients().size());
for (SlotDisplay input : shapedRecipe.ingredients()) {
List<ItemDescriptorWithCount> translated = translateToInput(session, input);
if (translated == null) {
continue;
}
inputs.add(translated);
if (translated.size() != 1 || translated.get(0) != ItemDescriptorWithCount.EMPTY) {
empty = false;
}
complexInputs |= translated.size() > 1;
}
if (empty) {
// Crashes Bedrock 1.19.70 otherwise
// Fixes https://github.com/GeyserMC/Geyser/issues/3549
continue;
}
if (complexInputs) {
System.out.println(inputs);
if (true) continue;
List<List<ItemDescriptorWithCount>> processedInputs = Lists.cartesianProduct(inputs);
System.out.println(processedInputs.size());
if (processedInputs.size() <= 500) { // Do not let us process giant lists.
List<String> bedrockRecipeIds = new ArrayList<>();
for (int i = 0; i < processedInputs.size(); i++) {
List<ItemDescriptorWithCount> possibleInput = processedInputs.get(i);
String recipeId = contents.id() + "_" + i;
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
shapedRecipe.width(), shapedRecipe.height(), possibleInput,
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
recipesPacket.getUnlockedRecipes().add(recipeId);
bedrockRecipeIds.add(recipeId);
}
javaToBedrockRecipeIds.put(contents.id(), bedrockRecipeIds);
continue;
}
}
String recipeId = Integer.toString(contents.id());
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
shapedRecipe.width(), shapedRecipe.height(), inputs.stream().map(descriptors -> descriptors.get(0)).toList(),
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
recipesPacket.getUnlockedRecipes().add(recipeId);
javaToBedrockRecipeIds.put(contents.id(), Collections.singletonList(recipeId));
javaToBedrockRecipeIds.put(contents.id(), List.copyOf(bedrockRecipeIds));
}
case CRAFTING_SHAPELESS -> {
ShapelessCraftingRecipeDisplay shapelessRecipe = (ShapelessCraftingRecipeDisplay) display;
Pair<Item, ItemData> pair = translateToOutput(session, shapelessRecipe.result());
if (pair == null || !pair.right().isValid()) {
// Likely modded item Bedrock will complain about
var bedrockRecipes = combinations(session, display, shapelessRecipe.ingredients());
if (bedrockRecipes == null) {
continue;
}
ItemData output = pair.right();
if (!(pair.left() instanceof BedrockRequiresTagItem)) {
// Strip NBT - tools won't appear in the recipe book otherwise
output = output.toBuilder().tag(null).build();
List<String> bedrockRecipeIds = new ArrayList<>();
ItemData output = bedrockRecipes.right();
List<List<ItemDescriptorWithCount>> left = bedrockRecipes.left();
for (int i = 0; i < left.size(); i++) {
List<ItemDescriptorWithCount> inputs = left.get(i);
String recipeId = contents.id() + "_" + i;
craftingDataPacket.getCraftingData().add(ShapelessRecipeData.shapeless(recipeId,
inputs, Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, RecipeUnlockingRequirement.INVALID));
recipesPacket.getUnlockedRecipes().add(recipeId);
bedrockRecipeIds.add(recipeId);
}
javaToBedrockRecipeIds.put(contents.id(), List.copyOf(bedrockRecipeIds));
}
case SMITHING -> {
if (true) {
@@ -299,4 +262,59 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
ItemMapping mapping = session.getItemMappings().getMapping(item);
return new ItemDescriptorWithCount(new DefaultDescriptor(mapping.getBedrockDefinition(), mapping.getBedrockData()), 1); // Need to check count
}
private Pair<List<List<ItemDescriptorWithCount>>, ItemData> combinations(GeyserSession session, RecipeDisplay display, List<SlotDisplay> ingredients) {
Pair<Item, ItemData> pair = translateToOutput(session, display.result());
if (pair == null || !pair.right().isValid()) {
// Likely modded item Bedrock will complain about
return null;
}
ItemData output = pair.right();
if (!(pair.left() instanceof BedrockRequiresTagItem)) {
// Strip NBT - tools won't appear in the recipe book otherwise
output = output.toBuilder().tag(null).build();
}
boolean empty = true;
boolean complexInputs = false;
List<List<ItemDescriptorWithCount>> inputs = new ArrayList<>(ingredients.size());
for (SlotDisplay input : ingredients) {
List<ItemDescriptorWithCount> translated = translateToInput(session, input);
if (translated == null) {
continue;
}
inputs.add(translated);
if (translated.size() != 1 || translated.get(0) != ItemDescriptorWithCount.EMPTY) {
empty = false;
}
complexInputs |= translated.size() > 1;
}
if (empty) {
// Crashes Bedrock 1.19.70 otherwise
// Fixes https://github.com/GeyserMC/Geyser/issues/3549
return null;
}
if (complexInputs) {
System.out.println(inputs);
long size = 1;
// See how big a cartesian product will get without creating one (Guava throws an error; not really ideal)
for (List<ItemDescriptorWithCount> list : inputs) {
size *= list.size();
if (size > 500) {
// Too much. No.
complexInputs = false;
break;
}
}
if (complexInputs) {
return Pair.of(Lists.cartesianProduct(inputs), output);
}
}
return Pair.of(
Collections.singletonList(inputs.stream().map(descriptors -> descriptors.get(0)).toList()),
output
);
}
}