mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge branch 'GeyserMC:master' into fix/protocol
This commit is contained in:
commit
4fa183e3c2
28 changed files with 677 additions and 61 deletions
|
@ -32,7 +32,6 @@ Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Ge
|
||||||
## What's Left to be Added/Fixed
|
## What's Left to be Added/Fixed
|
||||||
- Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you)
|
- Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you)
|
||||||
- Some Entity Flags
|
- Some Entity Flags
|
||||||
- Structure block UI
|
|
||||||
|
|
||||||
## What can't be fixed
|
## What can't be fixed
|
||||||
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page.
|
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page.
|
||||||
|
|
|
@ -51,7 +51,7 @@ public enum GeyserAttributeType {
|
||||||
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
|
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
|
||||||
|
|
||||||
// Bedrock Attributes
|
// Bedrock Attributes
|
||||||
ABSORPTION(null, "minecraft:absorption", 0f, Float.MAX_VALUE, 0f),
|
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),
|
||||||
EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 5f, 0f),
|
EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 5f, 0f),
|
||||||
EXPERIENCE(null, "minecraft:player.experience", 0f, 1f, 0f),
|
EXPERIENCE(null, "minecraft:player.experience", 0f, 1f, 0f),
|
||||||
EXPERIENCE_LEVEL(null, "minecraft:player.level", 0f, 24791.00f, 0f),
|
EXPERIENCE_LEVEL(null, "minecraft:player.level", 0f, 24791.00f, 0f),
|
||||||
|
@ -66,6 +66,10 @@ public enum GeyserAttributeType {
|
||||||
private final float maximum;
|
private final float maximum;
|
||||||
private final float defaultValue;
|
private final float defaultValue;
|
||||||
|
|
||||||
|
public AttributeData getAttribute() {
|
||||||
|
return getAttribute(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
public AttributeData getAttribute(float value) {
|
public AttributeData getAttribute(float value) {
|
||||||
return getAttribute(value, maximum);
|
return getAttribute(value, maximum);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,11 +200,9 @@ public class Entity implements GeyserEntity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Despawns the entity
|
* Despawns the entity
|
||||||
*
|
|
||||||
* @return can be deleted
|
|
||||||
*/
|
*/
|
||||||
public boolean despawnEntity() {
|
public void despawnEntity() {
|
||||||
if (!valid) return true;
|
if (!valid) return;
|
||||||
|
|
||||||
for (Entity passenger : passengers) { // Make sure all passengers on the despawned entity are updated
|
for (Entity passenger : passengers) { // Make sure all passengers on the despawned entity are updated
|
||||||
if (passenger == null) continue;
|
if (passenger == null) continue;
|
||||||
|
@ -218,7 +216,6 @@ public class Entity implements GeyserEntity {
|
||||||
session.sendUpstreamPacket(removeEntityPacket);
|
session.sendUpstreamPacket(removeEntityPacket);
|
||||||
|
|
||||||
valid = false;
|
valid = false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
|
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
|
||||||
|
|
|
@ -72,6 +72,9 @@ public class FireballEntity extends ThrowableEntity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
|
if (removedInVoid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false);
|
moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,9 @@ public class FishingHookEntity extends ThrowableEntity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
|
if (removedInVoid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (hooked || !isInAir() && !isInWater() || isOnGround()) {
|
if (hooked || !isInAir() && !isInWater() || isOnGround()) {
|
||||||
motion = Vector3f.ZERO;
|
motion = Vector3f.ZERO;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -74,7 +74,7 @@ public class ItemEntity extends ThrowableEntity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
if (isInWater()) {
|
if (removedInVoid() || isInWater()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
|
if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
|
||||||
|
|
|
@ -148,7 +148,7 @@ public class ItemFrameEntity extends Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean despawnEntity() {
|
public void despawnEntity() {
|
||||||
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
||||||
updateBlockPacket.setDataLayer(0);
|
updateBlockPacket.setDataLayer(0);
|
||||||
updateBlockPacket.setBlockPosition(bedrockPosition);
|
updateBlockPacket.setBlockPosition(bedrockPosition);
|
||||||
|
@ -161,7 +161,6 @@ public class ItemFrameEntity extends Entity {
|
||||||
session.getItemFrameCache().remove(bedrockPosition, this);
|
session.getItemFrameCache().remove(bedrockPosition, this);
|
||||||
|
|
||||||
valid = false;
|
valid = false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private NbtMap getDefaultTag() {
|
private NbtMap getDefaultTag() {
|
||||||
|
|
|
@ -55,6 +55,9 @@ public class ThrowableEntity extends Entity implements Tickable {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
|
if (removedInVoid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
|
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
|
||||||
float drag = getDrag();
|
float drag = getDrag();
|
||||||
float gravity = getGravity();
|
float gravity = getGravity();
|
||||||
|
@ -170,14 +173,14 @@ public class ThrowableEntity extends Entity implements Tickable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean despawnEntity() {
|
public void despawnEntity() {
|
||||||
if (definition.entityType() == EntityType.ENDER_PEARL) {
|
if (definition.entityType() == EntityType.ENDER_PEARL) {
|
||||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||||
particlePacket.setType(LevelEvent.PARTICLE_TELEPORT);
|
particlePacket.setType(LevelEvent.PARTICLE_TELEPORT);
|
||||||
particlePacket.setPosition(position);
|
particlePacket.setPosition(position);
|
||||||
session.sendUpstreamPacket(particlePacket);
|
session.sendUpstreamPacket(particlePacket);
|
||||||
}
|
}
|
||||||
return super.despawnEntity();
|
super.despawnEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -191,4 +194,17 @@ public class ThrowableEntity extends Entity implements Tickable {
|
||||||
moveAbsoluteImmediate(position, yaw, pitch, headYaw, isOnGround, teleported);
|
moveAbsoluteImmediate(position, yaw, pitch, headYaw, isOnGround, teleported);
|
||||||
lastJavaPosition = position;
|
lastJavaPosition = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the entity if it is 64 blocks below the world.
|
||||||
|
*
|
||||||
|
* @return true if the entity was removed
|
||||||
|
*/
|
||||||
|
public boolean removedInVoid() {
|
||||||
|
if (position.getY() < session.getDimensionType().minY() - 64) {
|
||||||
|
session.getEntityCache().removeEntity(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,11 +99,11 @@ public class ArmorStandEntity extends LivingEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean despawnEntity() {
|
public void despawnEntity() {
|
||||||
if (secondEntity != null) {
|
if (secondEntity != null) {
|
||||||
secondEntity.despawnEntity();
|
secondEntity.despawnEntity();
|
||||||
}
|
}
|
||||||
return super.despawnEntity();
|
super.despawnEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -148,11 +148,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean despawnEntity() {
|
public void despawnEntity() {
|
||||||
for (EnderDragonPartEntity part : allParts) {
|
for (EnderDragonPartEntity part : allParts) {
|
||||||
part.despawnEntity();
|
part.despawnEntity();
|
||||||
}
|
}
|
||||||
return super.despawnEntity();
|
super.despawnEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -55,6 +55,7 @@ import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
|
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||||
import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity;
|
import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity;
|
||||||
|
@ -283,7 +284,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||||
attributesPacket.setRuntimeEntityId(geyserId);
|
attributesPacket.setRuntimeEntityId(geyserId);
|
||||||
// Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit
|
// Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit
|
||||||
attributesPacket.setAttributes(Collections.singletonList(
|
attributesPacket.setAttributes(Collections.singletonList(
|
||||||
new AttributeData("minecraft:absorption", 0.0f, 1024f, entityMetadata.getPrimitiveValue(), 0.0f)));
|
GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue())));
|
||||||
session.sendUpstreamPacket(attributesPacket);
|
session.sendUpstreamPacket(attributesPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +308,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||||
* Sets the parrot occupying the shoulder. Bedrock Edition requires a full entity whereas Java Edition just
|
* Sets the parrot occupying the shoulder. Bedrock Edition requires a full entity whereas Java Edition just
|
||||||
* spawns it from the NBT data provided
|
* spawns it from the NBT data provided
|
||||||
*/
|
*/
|
||||||
private void setParrot(CompoundTag tag, boolean isLeft) {
|
protected void setParrot(CompoundTag tag, boolean isLeft) {
|
||||||
if (tag != null && !tag.isEmpty()) {
|
if (tag != null && !tag.isEmpty()) {
|
||||||
if ((isLeft && leftParrot != null) || (!isLeft && rightParrot != null)) {
|
if ((isLeft && leftParrot != null) || (!isLeft && rightParrot != null)) {
|
||||||
// No need to update a parrot's data when it already exists
|
// No need to update a parrot's data when it already exists
|
||||||
|
|
|
@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeTyp
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.GlobalPos;
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.GlobalPos;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -255,13 +256,51 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||||
return session.getAuthData().uuid();
|
return session.getAuthData().uuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAbsorptionHearts(FloatEntityMetadata entityMetadata) {
|
||||||
|
// The bedrock client can glitch when sending a health and absorption attribute in the same tick
|
||||||
|
// This can happen when switching servers. Resending the absorption attribute fixes the issue
|
||||||
|
attributes.put(GeyserAttributeType.ABSORPTION, GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue()));
|
||||||
|
super.setAbsorptionHearts(entityMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
public void resetMetadata() {
|
public void resetMetadata() {
|
||||||
// Reset all metadata to their default values
|
// Reset all metadata to their default values
|
||||||
// This is used when a player respawns
|
// This is used when a player respawns
|
||||||
|
this.flags.clear();
|
||||||
this.initializeMetadata();
|
this.initializeMetadata();
|
||||||
|
|
||||||
// Reset air
|
// Reset air
|
||||||
this.resetAir();
|
this.resetAir();
|
||||||
|
|
||||||
|
// Explicitly reset all metadata not handled by initializeMetadata
|
||||||
|
setParrot(null, true);
|
||||||
|
setParrot(null, false);
|
||||||
|
|
||||||
|
// Absorption is metadata in java edition
|
||||||
|
attributes.remove(GeyserAttributeType.ABSORPTION);
|
||||||
|
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||||
|
attributesPacket.setRuntimeEntityId(geyserId);
|
||||||
|
attributesPacket.setAttributes(Collections.singletonList(
|
||||||
|
GeyserAttributeType.ABSORPTION.getAttribute(0f)));
|
||||||
|
session.sendUpstreamPacket(attributesPacket);
|
||||||
|
|
||||||
|
dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, 0);
|
||||||
|
dirtyMetadata.put(EntityDataTypes.EFFECT_AMBIENCE, (byte) 0);
|
||||||
|
dirtyMetadata.put(EntityDataTypes.FREEZING_EFFECT_STRENGTH, 0f);
|
||||||
|
|
||||||
|
silent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetAttributes() {
|
||||||
|
attributes.clear();
|
||||||
|
maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue();
|
||||||
|
|
||||||
|
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||||
|
attributesPacket.setRuntimeEntityId(geyserId);
|
||||||
|
attributesPacket.setAttributes(Collections.singletonList(
|
||||||
|
GeyserAttributeType.MOVEMENT_SPEED.getAttribute()));
|
||||||
|
session.sendUpstreamPacket(attributesPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetAir() {
|
public void resetAir() {
|
||||||
|
|
|
@ -146,7 +146,7 @@ public final class BlockRegistryPopulator {
|
||||||
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
|
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
|
||||||
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
|
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
|
||||||
builder.remove("network_id"); // Added in 1.19.80 - ????
|
builder.remove("network_id"); // Added in 1.19.80 - ????
|
||||||
builder.remove("block_id"); // Added in 1.20.60 //TODO verify this can be just removed
|
builder.remove("block_id"); // Added in 1.20.60
|
||||||
//noinspection UnstableApiUsage
|
//noinspection UnstableApiUsage
|
||||||
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
|
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
|
||||||
vanillaBlockStates.set(i, builder.build());
|
vanillaBlockStates.set(i, builder.build());
|
||||||
|
@ -231,6 +231,7 @@ public final class BlockRegistryPopulator {
|
||||||
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
|
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>();
|
Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>();
|
||||||
|
Map<String, BlockDefinition> structureBlockDefinitions = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
BlockMappings.BlockMappingsBuilder builder = BlockMappings.builder();
|
BlockMappings.BlockMappingsBuilder builder = BlockMappings.builder();
|
||||||
while (blocksIterator.hasNext()) {
|
while (blocksIterator.hasNext()) {
|
||||||
|
@ -274,6 +275,18 @@ public final class BlockRegistryPopulator {
|
||||||
jigsawDefinitions.add(bedrockDefinition);
|
jigsawDefinitions.add(bedrockDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (javaId.contains("structure_block")) {
|
||||||
|
int modeIndex = javaId.indexOf("mode=");
|
||||||
|
if (modeIndex != -1) {
|
||||||
|
int startIndex = modeIndex + 5; // Length of "mode=" is 5
|
||||||
|
int endIndex = javaId.indexOf("]", startIndex);
|
||||||
|
if (endIndex != -1) {
|
||||||
|
String modeValue = javaId.substring(startIndex, endIndex);
|
||||||
|
structureBlockDefinitions.put(modeValue.toUpperCase(), bedrockDefinition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean waterlogged = entry.getKey().contains("waterlogged=true")
|
boolean waterlogged = entry.getKey().contains("waterlogged=true")
|
||||||
|| javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass");
|
|| javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass");
|
||||||
|
|
||||||
|
@ -360,6 +373,7 @@ public final class BlockRegistryPopulator {
|
||||||
.itemFrames(itemFrames)
|
.itemFrames(itemFrames)
|
||||||
.flowerPotBlocks(flowerPotBlocks)
|
.flowerPotBlocks(flowerPotBlocks)
|
||||||
.jigsawStates(jigsawDefinitions)
|
.jigsawStates(jigsawDefinitions)
|
||||||
|
.structureBlockStates(structureBlockDefinitions)
|
||||||
.remappedVanillaIds(remappedVanillaIds)
|
.remappedVanillaIds(remappedVanillaIds)
|
||||||
.blockProperties(customBlockProperties)
|
.blockProperties(customBlockProperties)
|
||||||
.customBlockStateDefinitions(customBlockStateDefinitions)
|
.customBlockStateDefinitions(customBlockStateDefinitions)
|
||||||
|
|
|
@ -61,6 +61,7 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
|
||||||
Map<String, NbtMap> flowerPotBlocks;
|
Map<String, NbtMap> flowerPotBlocks;
|
||||||
|
|
||||||
Set<BlockDefinition> jigsawStates;
|
Set<BlockDefinition> jigsawStates;
|
||||||
|
Map<String, BlockDefinition> structureBlockStates;
|
||||||
|
|
||||||
List<BlockPropertyData> blockProperties;
|
List<BlockPropertyData> blockProperties;
|
||||||
Object2ObjectMap<CustomBlockState, GeyserBedrockBlock> customBlockStateDefinitions;
|
Object2ObjectMap<CustomBlockState, GeyserBedrockBlock> customBlockStateDefinitions;
|
||||||
|
@ -96,6 +97,10 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BlockDefinition getStructureBlockFromMode(String mode) {
|
||||||
|
return structureBlockStates.get(mode);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable GeyserBedrockBlock getDefinition(int bedrockId) {
|
public @Nullable GeyserBedrockBlock getDefinition(int bedrockId) {
|
||||||
if (bedrockId < 0 || bedrockId >= this.bedrockRuntimeMap.length) {
|
if (bedrockId < 0 || bedrockId >= this.bedrockRuntimeMap.length) {
|
||||||
|
|
|
@ -214,6 +214,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
private final PistonCache pistonCache;
|
private final PistonCache pistonCache;
|
||||||
private final PreferencesCache preferencesCache;
|
private final PreferencesCache preferencesCache;
|
||||||
private final SkullCache skullCache;
|
private final SkullCache skullCache;
|
||||||
|
private final StructureBlockCache structureBlockCache;
|
||||||
private final TagCache tagCache;
|
private final TagCache tagCache;
|
||||||
private final WorldCache worldCache;
|
private final WorldCache worldCache;
|
||||||
|
|
||||||
|
@ -261,8 +262,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
@Setter
|
@Setter
|
||||||
private ItemMappings itemMappings;
|
private ItemMappings itemMappings;
|
||||||
|
|
||||||
private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = new Long2ObjectOpenHashMap<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required to decode biomes correctly.
|
* Required to decode biomes correctly.
|
||||||
*/
|
*/
|
||||||
|
@ -625,6 +624,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
this.pistonCache = new PistonCache(this);
|
this.pistonCache = new PistonCache(this);
|
||||||
this.preferencesCache = new PreferencesCache(this);
|
this.preferencesCache = new PreferencesCache(this);
|
||||||
this.skullCache = new SkullCache(this);
|
this.skullCache = new SkullCache(this);
|
||||||
|
this.structureBlockCache = new StructureBlockCache();
|
||||||
this.tagCache = new TagCache();
|
this.tagCache = new TagCache();
|
||||||
this.worldCache = new WorldCache(this);
|
this.worldCache = new WorldCache(this);
|
||||||
this.cameraData = new GeyserCameraData(this);
|
this.cameraData = new GeyserCameraData(this);
|
||||||
|
@ -712,7 +712,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
// Default move speed
|
// Default move speed
|
||||||
// Bedrock clients move very fast by default until they get an attribute packet correcting the speed
|
// Bedrock clients move very fast by default until they get an attribute packet correcting the speed
|
||||||
attributesPacket.setAttributes(Collections.singletonList(
|
attributesPacket.setAttributes(Collections.singletonList(
|
||||||
new AttributeData("minecraft:movement", 0.0f, 1024f, 0.1f, 0.1f)));
|
GeyserAttributeType.MOVEMENT_SPEED.getAttribute()));
|
||||||
upstream.sendPacket(attributesPacket);
|
upstream.sendPacket(attributesPacket);
|
||||||
|
|
||||||
GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket();
|
GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket();
|
||||||
|
|
|
@ -85,27 +85,29 @@ public class EntityCache {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeEntity(Entity entity, boolean force) {
|
public void removeEntity(Entity entity) {
|
||||||
if (entity instanceof PlayerEntity player) {
|
if (entity instanceof PlayerEntity player) {
|
||||||
session.getPlayerWithCustomHeads().remove(player.getUuid());
|
session.getPlayerWithCustomHeads().remove(player.getUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity != null && entity.isValid() && (force || entity.despawnEntity())) {
|
if (entity != null) {
|
||||||
|
if (entity.isValid()) {
|
||||||
|
entity.despawnEntity();
|
||||||
|
}
|
||||||
|
|
||||||
long geyserId = entityIdTranslations.remove(entity.getEntityId());
|
long geyserId = entityIdTranslations.remove(entity.getEntityId());
|
||||||
entities.remove(geyserId);
|
entities.remove(geyserId);
|
||||||
|
|
||||||
if (entity instanceof Tickable) {
|
if (entity instanceof Tickable) {
|
||||||
tickableEntities.remove(entity);
|
tickableEntities.remove(entity);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAllEntities() {
|
public void removeAllEntities() {
|
||||||
List<Entity> entities = new ArrayList<>(this.entities.values());
|
List<Entity> entities = new ArrayList<>(this.entities.values());
|
||||||
for (Entity entity : entities) {
|
for (Entity entity : entities) {
|
||||||
removeEntity(entity, false);
|
removeEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getPlayerWithCustomHeads().clear();
|
session.getPlayerWithCustomHeads().clear();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -23,28 +23,37 @@
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.bedrock;
|
package org.geysermc.geyser.session.cache;
|
||||||
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket;
|
import lombok.Getter;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.MapInfoRequestPacket;
|
import lombok.Setter;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public final class StructureBlockCache {
|
||||||
|
|
||||||
@Translator(packet = MapInfoRequestPacket.class)
|
/**
|
||||||
public class BedrockMapInfoRequestTranslator extends PacketTranslator<MapInfoRequestPacket> {
|
* Stores the current structure's name to be able to detect changes in the loaded structure
|
||||||
|
*/
|
||||||
|
private @Nullable String currentStructureName;
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void translate(GeyserSession session, MapInfoRequestPacket packet) {
|
* Stores the offset changes added by Geyser that ensure that structure bounds
|
||||||
long mapId = packet.getUniqueMapId();
|
* are the same for Java and Bedrock
|
||||||
|
*/
|
||||||
|
private @Nullable Vector3i bedrockOffset;
|
||||||
|
|
||||||
ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapId);
|
/**
|
||||||
if (mapPacket != null) {
|
* Stores the current structure block position while we're waiting on the Java
|
||||||
// Delay the packet 100ms to prevent the client from ignoring the packet
|
* server to send the data we need.
|
||||||
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(mapPacket),
|
*/
|
||||||
100, TimeUnit.MILLISECONDS);
|
private @Nullable Vector3i currentStructureBlock;
|
||||||
}
|
|
||||||
|
public void clear() {
|
||||||
|
this.currentStructureName = null;
|
||||||
|
this.currentStructureBlock = null;
|
||||||
|
this.bedrockOffset = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2024 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.geyser.translator.level.block.entity;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.geyser.util.StructureBlockUtils;
|
||||||
|
|
||||||
|
@BlockEntity(type = BlockEntityType.STRUCTURE_BLOCK)
|
||||||
|
public class StructureBlockBlockEntityTranslator extends BlockEntityTranslator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NbtMap getBlockEntityTag(GeyserSession session, BlockEntityType type, int x, int y, int z, CompoundTag tag, int blockState) {
|
||||||
|
// Sending a structure with size 0 doesn't clear the outline. Hence, we have to force it by replacing the block :/
|
||||||
|
int xStructureSize = getOrDefault(tag.get("sizeX"), 0);
|
||||||
|
int yStructureSize = getOrDefault(tag.get("sizeY"), 0);
|
||||||
|
int zStructureSize = getOrDefault(tag.get("sizeZ"), 0);
|
||||||
|
|
||||||
|
Vector3i size = Vector3i.from(xStructureSize, yStructureSize, zStructureSize);
|
||||||
|
|
||||||
|
if (size.equals(Vector3i.ZERO)) {
|
||||||
|
Vector3i position = Vector3i.from(x, y, z);
|
||||||
|
String mode = getOrDefault(tag.get("mode"), "");
|
||||||
|
|
||||||
|
// Set to air and back to reset the structure block
|
||||||
|
UpdateBlockPacket emptyBlockPacket = new UpdateBlockPacket();
|
||||||
|
emptyBlockPacket.setDataLayer(0);
|
||||||
|
emptyBlockPacket.setBlockPosition(position);
|
||||||
|
emptyBlockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
|
||||||
|
session.sendUpstreamPacket(emptyBlockPacket);
|
||||||
|
|
||||||
|
UpdateBlockPacket spawnerBlockPacket = new UpdateBlockPacket();
|
||||||
|
spawnerBlockPacket.setDataLayer(0);
|
||||||
|
spawnerBlockPacket.setBlockPosition(position);
|
||||||
|
spawnerBlockPacket.setDefinition(session.getBlockMappings().getStructureBlockFromMode(mode));
|
||||||
|
session.sendUpstreamPacket(spawnerBlockPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getBlockEntityTag(session, type, x, y, z, tag, blockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||||
|
if (tag.size() < 5) {
|
||||||
|
return; // These values aren't here
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.putString("structureName", getOrDefault(tag.get("name"), ""));
|
||||||
|
|
||||||
|
String mode = getOrDefault(tag.get("mode"), "");
|
||||||
|
int bedrockData = switch (mode) {
|
||||||
|
case "LOAD" -> 2;
|
||||||
|
case "CORNER" -> 3;
|
||||||
|
case "DATA" -> 4;
|
||||||
|
default -> 1; // SAVE
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.putInt("data", bedrockData);
|
||||||
|
builder.putString("dataField", ""); // ??? possibly related to Java's "metadata"
|
||||||
|
|
||||||
|
// Mirror behaves different in Java and Bedrock - it requires modifying the position in space as well
|
||||||
|
String mirror = getOrDefault(tag.get("mirror"), "");
|
||||||
|
StructureMirror bedrockMirror = switch (mirror) {
|
||||||
|
case "FRONT_BACK" -> StructureMirror.X;
|
||||||
|
case "LEFT_RIGHT" -> StructureMirror.Z;
|
||||||
|
default -> StructureMirror.NONE;
|
||||||
|
};
|
||||||
|
builder.putByte("mirror", (byte) bedrockMirror.ordinal());
|
||||||
|
|
||||||
|
builder.putByte("ignoreEntities", getOrDefault(tag.get("ignoreEntities"), (byte) 0));
|
||||||
|
builder.putByte("isPowered", getOrDefault(tag.get("powered"), (byte) 0));
|
||||||
|
builder.putLong("seed", getOrDefault(tag.get("seed"), 0L));
|
||||||
|
builder.putByte("showBoundingBox", getOrDefault(tag.get("showboundingbox"), (byte) 0));
|
||||||
|
|
||||||
|
String rotation = getOrDefault(tag.get("rotation"), "");
|
||||||
|
StructureRotation bedrockRotation = switch (rotation) {
|
||||||
|
case "CLOCKWISE_90" -> StructureRotation.ROTATE_90;
|
||||||
|
case "CLOCKWISE_180" -> StructureRotation.ROTATE_180;
|
||||||
|
case "COUNTERCLOCKWISE_90" -> StructureRotation.ROTATE_270;
|
||||||
|
default -> StructureRotation.NONE;
|
||||||
|
};
|
||||||
|
builder.putByte("rotation", (byte) bedrockRotation.ordinal());
|
||||||
|
|
||||||
|
int xStructureSize = getOrDefault(tag.get("sizeX"), 0);
|
||||||
|
int yStructureSize = getOrDefault(tag.get("sizeY"), 0);
|
||||||
|
int zStructureSize = getOrDefault(tag.get("sizeZ"), 0);
|
||||||
|
|
||||||
|
// The "positions" are also offsets on Java
|
||||||
|
int posX = getOrDefault(tag.get("posX"), 0);
|
||||||
|
int posY = getOrDefault(tag.get("posY"), 0);
|
||||||
|
int posZ = getOrDefault(tag.get("posZ"), 0);
|
||||||
|
|
||||||
|
Vector3i offset = StructureBlockUtils.calculateOffset(bedrockRotation, bedrockMirror,
|
||||||
|
xStructureSize, zStructureSize);
|
||||||
|
|
||||||
|
builder.putInt("xStructureOffset", posX + offset.getX());
|
||||||
|
builder.putInt("yStructureOffset", posY);
|
||||||
|
builder.putInt("zStructureOffset", posZ + offset.getZ());
|
||||||
|
|
||||||
|
builder.putInt("xStructureSize", xStructureSize);
|
||||||
|
builder.putInt("yStructureSize", yStructureSize);
|
||||||
|
builder.putInt("zStructureSize", zStructureSize);
|
||||||
|
|
||||||
|
builder.putFloat("integrity", getOrDefault(tag.get("integrity"), 0f)); // Is 1.0f by default on Java but 100.0f on Bedrock
|
||||||
|
|
||||||
|
// Java's "showair" is unrepresented
|
||||||
|
}
|
||||||
|
}
|
|
@ -348,6 +348,13 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
openPacket.setType(ContainerType.JIGSAW_EDITOR);
|
openPacket.setType(ContainerType.JIGSAW_EDITOR);
|
||||||
openPacket.setUniqueEntityId(-1);
|
openPacket.setUniqueEntityId(-1);
|
||||||
session.sendUpstreamPacket(openPacket);
|
session.sendUpstreamPacket(openPacket);
|
||||||
|
} else if (session.getBlockMappings().getStructureBlockStates().containsValue(packet.getBlockDefinition())) {
|
||||||
|
ContainerOpenPacket openPacket = new ContainerOpenPacket();
|
||||||
|
openPacket.setBlockPosition(packet.getBlockPosition());
|
||||||
|
openPacket.setId((byte) 1);
|
||||||
|
openPacket.setType(ContainerType.STRUCTURE_EDITOR);
|
||||||
|
openPacket.setUniqueEntityId(-1);
|
||||||
|
session.sendUpstreamPacket(openPacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2024 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.geyser.translator.protocol.bedrock;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureBlockType;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureEditorData;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.StructureBlockUpdatePacket;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
import org.geysermc.geyser.util.StructureBlockUtils;
|
||||||
|
|
||||||
|
@Translator(packet = StructureBlockUpdatePacket.class)
|
||||||
|
public class BedrockStructureBlockUpdateTranslator extends PacketTranslator<StructureBlockUpdatePacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, StructureBlockUpdatePacket packet) {
|
||||||
|
StructureEditorData data = packet.getEditorData();
|
||||||
|
|
||||||
|
UpdateStructureBlockAction action = UpdateStructureBlockAction.UPDATE_DATA;
|
||||||
|
if (packet.isPowered()) {
|
||||||
|
if (data.getType() == StructureBlockType.LOAD) {
|
||||||
|
action = UpdateStructureBlockAction.LOAD_STRUCTURE;
|
||||||
|
} else if (data.getType() == StructureBlockType.SAVE) {
|
||||||
|
action = UpdateStructureBlockAction.SAVE_STRUCTURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateStructureBlockMode mode = switch (data.getType()) {
|
||||||
|
case CORNER -> UpdateStructureBlockMode.CORNER;
|
||||||
|
case DATA -> UpdateStructureBlockMode.DATA;
|
||||||
|
case LOAD -> UpdateStructureBlockMode.LOAD;
|
||||||
|
default -> UpdateStructureBlockMode.SAVE;
|
||||||
|
};
|
||||||
|
|
||||||
|
StructureBlockUtils.sendJavaStructurePacket(session, packet.getBlockPosition(), data.getSettings().getSize(), mode, action, data.getSettings(),
|
||||||
|
data.isBoundingBoxVisible(), data.getName());
|
||||||
|
session.getStructureBlockCache().clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2024 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.geyser.translator.protocol.bedrock;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureSettings;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureTemplateRequestOperation;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.StructureTemplateDataRequestPacket;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
import org.geysermc.geyser.util.StructureBlockUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packet used in Bedrock to load structure size into the structure block GUI. It is sent every time the GUI is opened.
|
||||||
|
* Or, if the player updates the structure name. Which we can use to request the structure size from the Java server!
|
||||||
|
* <p>
|
||||||
|
* Java does not have this preview, instead, Java clients are forced out of the GUI to look at the area.
|
||||||
|
*/
|
||||||
|
@Translator(packet = StructureTemplateDataRequestPacket.class)
|
||||||
|
public class BedrockStructureTemplateDataRequestTranslator extends PacketTranslator<StructureTemplateDataRequestPacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, StructureTemplateDataRequestPacket packet) {
|
||||||
|
// All other operation types are ignored by Geyser since we do not support exporting/importing structures
|
||||||
|
if (packet.getOperation().equals(StructureTemplateRequestOperation.QUERY_SAVED_STRUCTURE)) {
|
||||||
|
Vector3i size = packet.getSettings().getSize();
|
||||||
|
StructureSettings settings = packet.getSettings();
|
||||||
|
|
||||||
|
// If we send a load packet to the Java server when the structure size is known, it would place the structure.
|
||||||
|
String currentStructureName = session.getStructureBlockCache().getCurrentStructureName();
|
||||||
|
|
||||||
|
// Case 1: Opening a structure block with information about structure size, but not yet saved by us
|
||||||
|
// Case 2: Getting an update from Bedrock with new information, doesn't bother us if it's the same structure
|
||||||
|
if (!packet.getSettings().getSize().equals(Vector3i.ZERO)) {
|
||||||
|
if (currentStructureName == null) {
|
||||||
|
Vector3i offset = StructureBlockUtils.calculateOffset(settings.getRotation(), settings.getMirror(),
|
||||||
|
settings.getSize().getX(), settings.getSize().getZ());
|
||||||
|
session.getStructureBlockCache().setBedrockOffset(offset);
|
||||||
|
session.getStructureBlockCache().setCurrentStructureName(packet.getName());
|
||||||
|
StructureBlockUtils.sendStructureData(session, size, packet.getName());
|
||||||
|
return;
|
||||||
|
} else if (packet.getName().equals(currentStructureName)) {
|
||||||
|
StructureBlockUtils.sendStructureData(session, size, packet.getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request a "structure load" from Java server, so it sends us the structure's size
|
||||||
|
// See the block entity translator for more info
|
||||||
|
session.getStructureBlockCache().setCurrentStructureBlock(packet.getPosition());
|
||||||
|
|
||||||
|
StructureBlockUtils.sendJavaStructurePacket(session,
|
||||||
|
packet.getPosition(),
|
||||||
|
Vector3i.ZERO, // We expect the Java server to tell us the size
|
||||||
|
UpdateStructureBlockMode.LOAD,
|
||||||
|
UpdateStructureBlockAction.LOAD_STRUCTURE,
|
||||||
|
settings,
|
||||||
|
true,
|
||||||
|
packet.getName()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
StructureBlockUtils.sendEmptyStructureData(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,7 +71,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
||||||
// Remove all bossbars
|
// Remove all bossbars
|
||||||
session.getEntityCache().removeAllBossBars();
|
session.getEntityCache().removeAllBossBars();
|
||||||
// Remove extra hearts, hunger, etc.
|
// Remove extra hearts, hunger, etc.
|
||||||
entity.getAttributes().clear();
|
entity.resetAttributes();
|
||||||
entity.resetMetadata();
|
entity.resetMetadata();
|
||||||
|
|
||||||
// Reset weather
|
// Reset weather
|
||||||
|
|
|
@ -49,6 +49,14 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
|
||||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||||
PlayerSpawnInfo spawnInfo = packet.getCommonPlayerSpawnInfo();
|
PlayerSpawnInfo spawnInfo = packet.getCommonPlayerSpawnInfo();
|
||||||
|
|
||||||
|
if (!packet.isKeepMetadata()) {
|
||||||
|
entity.resetMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!packet.isKeepAttributes()) {
|
||||||
|
entity.resetAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
session.setSpawned(false);
|
session.setSpawned(false);
|
||||||
|
|
||||||
entity.setHealth(entity.getMaxHealth());
|
entity.setHealth(entity.getMaxHealth());
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class JavaRemoveEntitiesTranslator extends PacketTranslator<ClientboundRe
|
||||||
for (int entityId : packet.getEntityIds()) {
|
for (int entityId : packet.getEntityIds()) {
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId(entityId);
|
Entity entity = session.getEntityCache().getEntityByJavaId(entityId);
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
session.getEntityCache().removeEntity(entity, false);
|
session.getEntityCache().removeEntity(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,13 @@ package org.geysermc.geyser.translator.protocol.java.level;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundBlockEntityDataPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundBlockEntityDataPacket;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||||
import org.cloudburstmc.math.vector.Vector3i;
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||||
|
@ -41,6 +45,7 @@ import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTransla
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||||
|
import org.geysermc.geyser.util.StructureBlockUtils;
|
||||||
|
|
||||||
@Translator(packet = ClientboundBlockEntityDataPacket.class)
|
@Translator(packet = ClientboundBlockEntityDataPacket.class)
|
||||||
public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundBlockEntityDataPacket> {
|
public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundBlockEntityDataPacket> {
|
||||||
|
@ -95,5 +100,59 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
|
||||||
openPacket.setUniqueEntityId(-1);
|
openPacket.setUniqueEntityId(-1);
|
||||||
session.sendUpstreamPacket(openPacket);
|
session.sendUpstreamPacket(openPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When a Java client is trying to load a structure, it expects the server to send it the size of the structure.
|
||||||
|
// On 1.20.4, the server does so here - we can pass that through to Bedrock, so we're properly selecting the area.
|
||||||
|
if (type == BlockEntityType.STRUCTURE_BLOCK && session.getGameMode() == GameMode.CREATIVE
|
||||||
|
&& packet.getPosition().equals(session.getStructureBlockCache().getCurrentStructureBlock())
|
||||||
|
&& packet.getNbt() != null && packet.getNbt().size() > 5
|
||||||
|
) {
|
||||||
|
CompoundTag map = packet.getNbt();
|
||||||
|
|
||||||
|
String mode = getOrDefault(map.get("mode"), "");
|
||||||
|
if (!mode.equalsIgnoreCase("LOAD")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mirror = getOrDefault(map.get("mirror"), "");
|
||||||
|
StructureMirror bedrockMirror = switch (mirror) {
|
||||||
|
case "FRONT_BACK" -> StructureMirror.X;
|
||||||
|
case "LEFT_RIGHT" -> StructureMirror.Z;
|
||||||
|
default -> StructureMirror.NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
String rotation = getOrDefault(map.get("rotation"), "");
|
||||||
|
StructureRotation bedrockRotation = switch (rotation) {
|
||||||
|
case "CLOCKWISE_90" -> StructureRotation.ROTATE_90;
|
||||||
|
case "CLOCKWISE_180" -> StructureRotation.ROTATE_180;
|
||||||
|
case "COUNTERCLOCKWISE_90" -> StructureRotation.ROTATE_270;
|
||||||
|
default -> StructureRotation.NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
String name = getOrDefault(map.get("name"), "");
|
||||||
|
int sizeX = getOrDefault(map.get("sizeX"), 0);
|
||||||
|
int sizeY = getOrDefault(map.get("sizeY"), 0);
|
||||||
|
int sizeZ = getOrDefault(map.get("sizeZ"), 0);
|
||||||
|
|
||||||
|
session.getStructureBlockCache().setCurrentStructureBlock(null);
|
||||||
|
|
||||||
|
Vector3i size = Vector3i.from(sizeX, sizeY, sizeZ);
|
||||||
|
if (size.equals(Vector3i.ZERO)) {
|
||||||
|
StructureBlockUtils.sendEmptyStructureData(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i offset = StructureBlockUtils.calculateOffset(bedrockRotation, bedrockMirror,
|
||||||
|
sizeX, sizeZ);
|
||||||
|
session.getStructureBlockCache().setBedrockOffset(offset);
|
||||||
|
session.getStructureBlockCache().setCurrentStructureName(name);
|
||||||
|
StructureBlockUtils.sendStructureData(session, size, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected <T> T getOrDefault(Tag tag, T defaultValue) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,6 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundMapItemDataPacket packet) {
|
public void translate(GeyserSession session, ClientboundMapItemDataPacket packet) {
|
||||||
org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket mapItemDataPacket = new org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket();
|
org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket mapItemDataPacket = new org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket();
|
||||||
boolean shouldStore = false;
|
|
||||||
|
|
||||||
mapItemDataPacket.setUniqueMapId(packet.getMapId());
|
mapItemDataPacket.setUniqueMapId(packet.getMapId());
|
||||||
mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
||||||
|
@ -61,11 +60,6 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
|
||||||
mapItemDataPacket.setWidth(data.getColumns());
|
mapItemDataPacket.setWidth(data.getColumns());
|
||||||
mapItemDataPacket.setHeight(data.getRows());
|
mapItemDataPacket.setHeight(data.getRows());
|
||||||
|
|
||||||
// We have a full map image, this usually only happens on spawn for the initial image
|
|
||||||
if (mapItemDataPacket.getWidth() == 128 && mapItemDataPacket.getHeight() == 128) {
|
|
||||||
shouldStore = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every int entry is an ARGB color
|
// Every int entry is an ARGB color
|
||||||
int[] colors = new int[data.getData().length];
|
int[] colors = new int[data.getData().length];
|
||||||
|
|
||||||
|
@ -87,12 +81,11 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
|
||||||
id++;
|
id++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the map to send when the client requests it, as bedrock expects the data after a MapInfoRequestPacket
|
// Client will ignore if sent too early
|
||||||
if (shouldStore) {
|
if (session.isSentSpawnPacket()) {
|
||||||
session.getStoredMaps().put(mapItemDataPacket.getUniqueMapId(), mapItemDataPacket);
|
session.sendUpstreamPacket(mapItemDataPacket);
|
||||||
|
} else {
|
||||||
|
session.getUpstream().queuePostStartGamePacket(mapItemDataPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send anyway just in case
|
|
||||||
session.sendUpstreamPacket(mapItemDataPacket);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ public class InventoryUtils {
|
||||||
InventoryTranslator translator = session.getInventoryTranslator();
|
InventoryTranslator translator = session.getInventoryTranslator();
|
||||||
translator.closeInventory(session, inventory);
|
translator.closeInventory(session, inventory);
|
||||||
if (confirm && inventory.isDisplayed() && !inventory.isPending()
|
if (confirm && inventory.isDisplayed() && !inventory.isPending()
|
||||||
&& !(translator instanceof LecternInventoryTranslator) // TODO: double-check
|
&& !(translator instanceof LecternInventoryTranslator) // Closing lecterns is not followed with a close confirmation
|
||||||
) {
|
) {
|
||||||
session.setClosingInventory(true);
|
session.setClosingInventory(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2024 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.geyser.util;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode;
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetStructureBlockPacket;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
|
import org.cloudburstmc.nbt.NbtList;
|
||||||
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureSettings;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.structure.StructureTemplateResponseType;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.StructureTemplateDataResponsePacket;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
||||||
|
public class StructureBlockUtils {
|
||||||
|
|
||||||
|
private static final NbtMap EMPTY_STRUCTURE_DATA;
|
||||||
|
|
||||||
|
static {
|
||||||
|
NbtMapBuilder builder = NbtMap.builder();
|
||||||
|
builder.putInt("format_version", 1);
|
||||||
|
builder.putCompound("structure", NbtMap.builder()
|
||||||
|
.putList("block_indices", NbtType.LIST, NbtList.EMPTY, NbtList.EMPTY)
|
||||||
|
.putList("entities", NbtType.COMPOUND)
|
||||||
|
.putCompound("palette", NbtMap.EMPTY)
|
||||||
|
.build());
|
||||||
|
builder.putList("structure_world_origin", NbtType.INT, 0, 0, 0);
|
||||||
|
EMPTY_STRUCTURE_DATA = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendEmptyStructureData(GeyserSession session) {
|
||||||
|
StructureTemplateDataResponsePacket responsePacket = new StructureTemplateDataResponsePacket();
|
||||||
|
responsePacket.setName("");
|
||||||
|
responsePacket.setSave(false);
|
||||||
|
responsePacket.setType(StructureTemplateResponseType.QUERY);
|
||||||
|
session.sendUpstreamPacket(responsePacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendStructureData(GeyserSession session,Vector3i size, String name) {
|
||||||
|
StructureTemplateDataResponsePacket responsePacket = new StructureTemplateDataResponsePacket();
|
||||||
|
responsePacket.setName(name);
|
||||||
|
responsePacket.setSave(true);
|
||||||
|
responsePacket.setTag(EMPTY_STRUCTURE_DATA.toBuilder()
|
||||||
|
// Bedrock does not like negative sizes here
|
||||||
|
.putList("size", NbtType.INT, Math.abs(size.getX()), size.getY(), Math.abs(size.getZ()))
|
||||||
|
.build());
|
||||||
|
responsePacket.setType(StructureTemplateResponseType.QUERY);
|
||||||
|
session.sendUpstreamPacket(responsePacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3i calculateOffset(StructureRotation structureRotation, StructureMirror structureMirror,
|
||||||
|
int sizeX, int sizeZ) {
|
||||||
|
int newOffsetX = 0;
|
||||||
|
int newOffsetZ = 0;
|
||||||
|
|
||||||
|
switch (structureRotation) {
|
||||||
|
case ROTATE_90 -> {
|
||||||
|
switch (structureMirror) {
|
||||||
|
case NONE -> newOffsetX -= sizeZ - 1;
|
||||||
|
case X -> {
|
||||||
|
newOffsetZ -= sizeX - 1;
|
||||||
|
newOffsetX -= sizeZ - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ROTATE_180 -> {
|
||||||
|
switch (structureMirror) {
|
||||||
|
case NONE -> {
|
||||||
|
newOffsetX -= sizeX - 1;
|
||||||
|
newOffsetZ -= sizeZ - 1;
|
||||||
|
}
|
||||||
|
case Z -> newOffsetX -= sizeX - 1;
|
||||||
|
case X -> newOffsetZ -= sizeZ - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ROTATE_270 -> {
|
||||||
|
switch (structureMirror) {
|
||||||
|
case NONE -> newOffsetZ -= sizeX - 1;
|
||||||
|
case Z -> {
|
||||||
|
newOffsetZ -= sizeX - 1;
|
||||||
|
newOffsetX -= sizeZ - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
switch (structureMirror) {
|
||||||
|
case Z -> newOffsetZ -= sizeZ - 1;
|
||||||
|
case X -> newOffsetX -= sizeX - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Vector3i.from(newOffsetX, 0, newOffsetZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendJavaStructurePacket(GeyserSession session, Vector3i blockPosition, Vector3i size, UpdateStructureBlockMode mode, UpdateStructureBlockAction action,
|
||||||
|
StructureSettings settings, boolean boundingBoxVisible, String structureName) {
|
||||||
|
|
||||||
|
com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror mirror = switch (settings.getMirror()) {
|
||||||
|
case X -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.FRONT_BACK;
|
||||||
|
case Z -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.LEFT_RIGHT;
|
||||||
|
default -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation rotation = switch (settings.getRotation()) {
|
||||||
|
case ROTATE_90 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.CLOCKWISE_90;
|
||||||
|
case ROTATE_180 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.CLOCKWISE_180;
|
||||||
|
case ROTATE_270 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.COUNTERCLOCKWISE_90;
|
||||||
|
default -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3i offset = settings.getOffset();
|
||||||
|
if (session.getStructureBlockCache().getBedrockOffset() != null) {
|
||||||
|
offset = settings.getOffset().sub(session.getStructureBlockCache().getBedrockOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerboundSetStructureBlockPacket structureBlockPacket = new ServerboundSetStructureBlockPacket(
|
||||||
|
blockPosition,
|
||||||
|
action,
|
||||||
|
mode,
|
||||||
|
structureName,
|
||||||
|
offset,
|
||||||
|
settings.getSize(),
|
||||||
|
mirror,
|
||||||
|
rotation,
|
||||||
|
"",
|
||||||
|
settings.getIntegrityValue(),
|
||||||
|
settings.getIntegritySeed(),
|
||||||
|
settings.isIgnoringEntities(),
|
||||||
|
false,
|
||||||
|
boundingBoxVisible
|
||||||
|
);
|
||||||
|
|
||||||
|
session.sendDownstreamPacket(structureBlockPacket);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue