Merge branch 'master' into master

This commit is contained in:
Jason 2020-04-19 14:09:36 -07:00 committed by GitHub
commit 12b99c5b1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
108 changed files with 2559 additions and 440 deletions

View file

@ -30,16 +30,10 @@
<version>2.9.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry</artifactId>
<version>1.7.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.protocol</groupId>
<artifactId>bedrock-v389</artifactId>
<version>2.5.4</version>
<artifactId>bedrock-v390</artifactId>
<version>2.5.5-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>

View file

@ -27,7 +27,7 @@ package org.geysermc.connector;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.BedrockServer;
import com.nukkitx.protocol.bedrock.v389.Bedrock_v389;
import com.nukkitx.protocol.bedrock.v390.Bedrock_v390;
import lombok.Getter;
@ -49,6 +49,8 @@ import java.net.InetSocketAddress;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@ -56,12 +58,12 @@ import java.util.concurrent.TimeUnit;
@Getter
public class GeyserConnector {
public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v389.V389_CODEC;
public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v390.V390_CODEC;
public static final String NAME = "Geyser";
public static final String VERSION = "1.0-SNAPSHOT";
private final Map<Object, GeyserSession> players = new HashMap<>();
private final Map<InetSocketAddress, GeyserSession> players = new HashMap<>();
private static GeyserConnector instance;
@ -141,6 +143,40 @@ public class GeyserConnector {
bootstrap.getGeyserLogger().info("Shutting down Geyser.");
shuttingDown = true;
if (players.size() >= 1) {
bootstrap.getGeyserLogger().info("Kicking " + players.size() + " player(s)");
for (GeyserSession playerSession : players.values()) {
playerSession.disconnect("Geyser Proxy shutting down.");
}
CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
// Simulate a long-running Job
try {
while (true) {
if (players.size() == 0) {
return;
}
TimeUnit.MILLISECONDS.sleep(100);
}
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
});
// Block and wait for the future to complete
try {
future.get();
bootstrap.getGeyserLogger().info("Kicked all players");
} catch (Exception e) {
// Quietly fail
}
}
generalThreadPool.shutdown();
bedrockServer.close();
players.clear();
@ -148,17 +184,15 @@ public class GeyserConnector {
authType = null;
commandMap.getCommands().clear();
commandMap = null;
bootstrap.getGeyserLogger().info("Geyser shutdown successfully.");
}
public void addPlayer(GeyserSession player) {
players.put(player.getAuthData().getName(), player);
players.put(player.getAuthData().getUUID(), player);
players.put(player.getSocketAddress(), player);
}
public void removePlayer(GeyserSession player) {
players.remove(player.getAuthData().getName());
players.remove(player.getAuthData().getUUID());
players.remove(player.getSocketAddress());
}

View file

@ -48,6 +48,11 @@ public class StopCommand extends GeyserCommand {
if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) {
return;
}
connector.shutdown();
if (connector.getPlatformType() == PlatformType.STANDALONE) {
System.exit(0);
}
}
}

View file

@ -0,0 +1,82 @@
/*
* 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.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class EnderCrystalEntity extends Entity {
public EnderCrystalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// Show beam
// Usually performed client-side on Bedrock except for Ender Dragon respawn event
if (entityMetadata.getId() == 7) {
if (entityMetadata.getValue() instanceof Position) {
Position pos = (Position) entityMetadata.getValue();
metadata.put(EntityData.BLOCK_TARGET, Vector3i.from(pos.getX(), pos.getY(), pos.getZ()));
} else {
metadata.put(EntityData.BLOCK_TARGET, Vector3i.ZERO);
}
}
// There is a base located on the ender crystal
if (entityMetadata.getId() == 8) {
metadata.getFlags().setFlag(EntityFlag.SHOW_BOTTOM, (boolean) entityMetadata.getValue());
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void spawnEntity(GeyserSession session) {
AddEntityPacket addEntityPacket = new AddEntityPacket();
// Not end crystal but ender crystal
addEntityPacket.setIdentifier("minecraft:ender_crystal");
addEntityPacket.setRuntimeEntityId(geyserId);
addEntityPacket.setUniqueEntityId(geyserId);
addEntityPacket.setPosition(position);
addEntityPacket.setMotion(motion);
addEntityPacket.setRotation(getBedrockRotation());
addEntityPacket.setEntityType(entityType.getType());
addEntityPacket.getMetadata().putAll(metadata);
valid = true;
session.getUpstream().sendPacket(addEntityPacket);
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.entity;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.translators.block.BlockTranslator;
public class FallingBlockEntity extends Entity {
public FallingBlockEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, int javaId) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.metadata.put(EntityData.VARIANT, BlockTranslator.getBedrockBlockId(javaId));
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.entity;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class FishingHookEntity extends Entity {
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void spawnEntity(GeyserSession session) {
AddEntityPacket addEntityPacket = new AddEntityPacket();
// Different ID in Bedrock
addEntityPacket.setIdentifier("minecraft:fishing_hook");
addEntityPacket.setRuntimeEntityId(geyserId);
addEntityPacket.setUniqueEntityId(geyserId);
addEntityPacket.setPosition(position);
addEntityPacket.setMotion(motion);
addEntityPacket.setRotation(getBedrockRotation());
addEntityPacket.setEntityType(entityType.getType());
addEntityPacket.getMetadata().putAll(metadata);
valid = true;
session.getUpstream().sendPacket(addEntityPacket);
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
}
}

View file

@ -26,8 +26,11 @@
package org.geysermc.connector.entity;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.message.TextMessage;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.CommandPermission;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
@ -39,6 +42,8 @@ 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.scoreboard.Team;
import org.geysermc.connector.utils.MessageUtils;
import org.geysermc.connector.utils.SkinUtils;
import java.util.UUID;
@ -152,4 +157,27 @@ public class PlayerEntity extends LivingEntity {
public void setPosition(Vector3f position) {
this.position = position.add(0, entityType.getOffset(), 0);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 2) {
// System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet());
for (Team team : session.getScoreboardCache().getScoreboard().getTeams().values()) {
// session.getConnector().getLogger().info("team name " + team.getName());
// session.getConnector().getLogger().info("team entities " + team.getEntities());
}
String username = this.username;
TextMessage name = (TextMessage) entityMetadata.getValue();
if (name != null) {
username = MessageUtils.getBedrockMessage(name);
}
Team team = session.getScoreboardCache().getScoreboard().getTeamFor(username);
if (team != null) {
// session.getConnector().getLogger().info("team name es " + team.getName() + " with prefix " + team.getPrefix() + " and suffix " + team.getSuffix());
metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix());
}
}
}
}

View file

@ -42,6 +42,8 @@ public class BeeEntity extends AnimalEntity {
if (entityMetadata.getId() == 16) {
byte xd = (byte) entityMetadata.getValue();
metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02);
// If the bee has nectar or not
metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08);
}
super.updateBedrockMetadata(entityMetadata, session);
}

View file

@ -25,12 +25,52 @@
package org.geysermc.connector.entity.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.ItemData;
import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.block.BlockTranslator;
public class LlamaEntity extends ChestedHorseEntity {
public LlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// Strength
if (entityMetadata.getId() == 19) {
metadata.put(EntityData.STRENGTH, entityMetadata.getValue());
}
// Color equipped on the llama
if (entityMetadata.getId() == 20) {
// Bedrock treats llama decoration as armor
MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket();
equipmentPacket.setRuntimeEntityId(getGeyserId());
// -1 means no armor
if ((int) entityMetadata.getValue() != -1) {
// The damage value is the dye color that Java sends us
// Always going to be a carpet so we can hardcode 171 in BlockTranslator
// The int then short conversion is required or we get a ClassCastException
equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short)((int) entityMetadata.getValue()), 1));
} else {
equipmentPacket.setChestplate(ItemData.AIR);
}
// Required to fill out the rest of the equipment or Bedrock ignores it, including above else statement if removing armor
equipmentPacket.setBoots(ItemData.AIR);
equipmentPacket.setHelmet(ItemData.AIR);
equipmentPacket.setLeggings(ItemData.AIR);
session.getUpstream().sendPacket(equipmentPacket);
}
// Color of the llama
if (entityMetadata.getId() == 21) {
metadata.put(EntityData.VARIANT, entityMetadata.getValue());
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.entity.living.animal.horse;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class TraderLlamaEntity extends LlamaEntity {
public TraderLlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void spawnEntity(GeyserSession session) {
// The trader llama is a separate entity from the llama in Java but a normal llama with extra metadata in Bedrock.
AddEntityPacket addEntityPacket = new AddEntityPacket();
addEntityPacket.setIdentifier("minecraft:llama");
addEntityPacket.setRuntimeEntityId(geyserId);
addEntityPacket.setUniqueEntityId(geyserId);
addEntityPacket.setPosition(position);
addEntityPacket.setMotion(motion);
addEntityPacket.setRotation(getBedrockRotation());
addEntityPacket.setEntityType(entityType.getType());
addEntityPacket.getMetadata().putAll(metadata);
// Here's the difference
addEntityPacket.getMetadata().put(EntityData.MARK_VARIANT, 1);
valid = true;
session.getUpstream().sendPacket(addEntityPacket);
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.entity.living.animal.tameable;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -40,11 +41,31 @@ public class CatEntity extends TameableEntity {
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 18) {
metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue());
// Different colors in Java and Bedrock for some reason
int variantColor;
switch ((int) entityMetadata.getValue()) {
case 0:
variantColor = 8;
break;
case 8:
variantColor = 0;
break;
case 9:
variantColor = 10;
break;
case 10:
variantColor = 9;
break;
default:
variantColor = (int) entityMetadata.getValue();
}
metadata.put(EntityData.VARIANT, variantColor);
}
if (entityMetadata.getId() == 21) {
// FIXME: Colors the whole animal instead of just collar
metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue());
// Needed or else wild cats are a red color
if (metadata.getFlags().getFlag(EntityFlag.TAMED)) {
metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue());
}
}
super.updateBedrockMetadata(entityMetadata, session);
}

View file

@ -0,0 +1,48 @@
/*
* 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.entity.living.animal.tameable;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class ParrotEntity extends TameableEntity {
public ParrotEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// Parrot color
if (entityMetadata.getId() == 18) {
metadata.put(EntityData.VARIANT, entityMetadata.getValue());
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -27,6 +27,7 @@ package org.geysermc.connector.entity.living.animal.tameable;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import org.geysermc.connector.entity.living.animal.AnimalEntity;
import org.geysermc.connector.entity.type.EntityType;
@ -45,6 +46,11 @@ public class TameableEntity extends AnimalEntity {
metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01);
metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02);
metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04);
// Must be set for wolf collar color to work
// Extending it to all entities to prevent future bugs
if (metadata.getFlags().getFlag(EntityFlag.TAMED)) {
metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId());
} // Can't de-tame an entity so no resetting the owner ID
}
super.updateBedrockMetadata(entityMetadata, session);
}

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.entity.living.animal.tameable;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -39,9 +40,14 @@ public class WolfEntity extends TameableEntity {
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head
if (entityMetadata.getId() == 18) {
metadata.getFlags().setFlag(EntityFlag.INTERESTED, (boolean) entityMetadata.getValue());
}
// Wolf collar color
// Relies on EntityData.OWNER_EID being set in TameableEntity.java
if (entityMetadata.getId() == 19) {
// FIXME: Colors the whole animal instead of just collar
// metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue());
metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue());
}
super.updateBedrockMetadata(entityMetadata, session);
}

View file

@ -23,9 +23,10 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.entity.living;
package org.geysermc.connector.entity.living.merchant;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.living.AgeableEntity;
import org.geysermc.connector.entity.type.EntityType;
public class AbstractMerchantEntity extends AgeableEntity {

View file

@ -0,0 +1,108 @@
/*
* 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.entity.living.merchant;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class VillagerEntity extends AbstractMerchantEntity {
private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap();
private static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap();
static {
// Java villager profession IDs -> Bedrock
VILLAGER_VARIANTS.put(0, 0);
VILLAGER_VARIANTS.put(1, 8);
VILLAGER_VARIANTS.put(2, 11);
VILLAGER_VARIANTS.put(3, 6);
VILLAGER_VARIANTS.put(4, 7);
VILLAGER_VARIANTS.put(5, 1);
VILLAGER_VARIANTS.put(6, 2);
VILLAGER_VARIANTS.put(7, 4);
VILLAGER_VARIANTS.put(8, 12);
VILLAGER_VARIANTS.put(9, 5);
VILLAGER_VARIANTS.put(10, 13);
VILLAGER_VARIANTS.put(11, 14);
VILLAGER_VARIANTS.put(12, 3);
VILLAGER_VARIANTS.put(13, 10);
VILLAGER_VARIANTS.put(14, 9);
VILLAGER_REGIONS.put(0, 1);
VILLAGER_REGIONS.put(1, 2);
VILLAGER_REGIONS.put(2, 0);
VILLAGER_REGIONS.put(3, 3);
VILLAGER_REGIONS.put(4, 4);
VILLAGER_REGIONS.put(5, 5);
VILLAGER_REGIONS.put(6, 6);
}
public VillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 17) {
VillagerData villagerData = (VillagerData) entityMetadata.getValue();
// Profession
metadata.put(EntityData.VARIANT, VILLAGER_VARIANTS.get(villagerData.getProfession()));
//metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason?
// Region
metadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType()));
// Trade tier - different indexing in Bedrock
metadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1);
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void spawnEntity(GeyserSession session) {
AddEntityPacket addEntityPacket = new AddEntityPacket();
// "v2" or else it's the legacy villager
addEntityPacket.setIdentifier("minecraft:villager_v2");
addEntityPacket.setRuntimeEntityId(geyserId);
addEntityPacket.setUniqueEntityId(geyserId);
addEntityPacket.setPosition(position);
addEntityPacket.setMotion(motion);
addEntityPacket.setRotation(getBedrockRotation());
addEntityPacket.setEntityType(entityType.getType());
addEntityPacket.getMetadata().putAll(metadata);
valid = true;
session.getUpstream().sendPacket(addEntityPacket);
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
}
}

View file

@ -0,0 +1,86 @@
/*
* 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.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.Attribute;
import com.nukkitx.protocol.bedrock.data.EntityEventType;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.living.InsentientEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class EnderDragonEntity extends InsentientEntity {
public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 15) {
metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
switch ((int) entityMetadata.getValue()) {
// Performing breath attack
case 5:
EntityEventPacket entityEventPacket = new EntityEventPacket();
entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
entityEventPacket.setRuntimeEntityId(geyserId);
entityEventPacket.setData(0);
session.getUpstream().sendPacket(entityEventPacket);
case 6:
case 7:
metadata.getFlags().setFlag(EntityFlag.SITTING, true);
break;
}
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void spawnEntity(GeyserSession session) {
AddEntityPacket addEntityPacket = new AddEntityPacket();
addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase());
addEntityPacket.setRuntimeEntityId(geyserId);
addEntityPacket.setUniqueEntityId(geyserId);
addEntityPacket.setPosition(position);
addEntityPacket.setMotion(motion);
addEntityPacket.setRotation(getBedrockRotation());
addEntityPacket.setEntityType(entityType.getType());
addEntityPacket.getMetadata().putAll(metadata);
// Otherwise dragon is always 'dying'
addEntityPacket.getAttributes().add(new Attribute("minecraft:health", 0.0f, 200f, 200f, 200f));
valid = true;
session.getUpstream().sendPacket(addEntityPacket);
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.block.BlockTranslator;
public class EndermanEntity extends MonsterEntity {
public EndermanEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// Held block
if (entityMetadata.getId() == 15) {
metadata.put(EntityData.ENDERMAN_HELD_ITEM_ID, BlockTranslator.getBedrockBlockId((BlockState) entityMetadata.getValue()));
}
// 'Angry' - mouth open
if (entityMetadata.getId() == 16) {
metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue());
}
// TODO: ID 17 is stared at but I don't believe it's used - maybe only for the sound effect. Check after particle merge
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -0,0 +1,67 @@
/*
* 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.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.EntityData;
import org.geysermc.connector.entity.living.GolemEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class ShulkerEntity extends GolemEntity {
public ShulkerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 15) {
BlockFace blockFace = (BlockFace) entityMetadata.getValue();
metadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) blockFace.ordinal());
}
if (entityMetadata.getId() == 16) {
Position position = (Position) entityMetadata.getValue();
if (position != null) {
metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ()));
}
}
//TODO Outdated metadata flag SHULKER_PEAK_HEIGHT
// if (entityMetadata.getId() == 17) {
// int height = (byte) entityMetadata.getValue();
// metadata.put(EntityData.SHULKER_PEAK_HEIGHT, height);
// }
if (entityMetadata.getId() == 18) {
int color = Math.abs((byte) entityMetadata.getValue() - 15);
metadata.put(EntityData.VARIANT, color);
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -29,13 +29,9 @@ import lombok.Getter;
import org.geysermc.connector.entity.*;
import org.geysermc.connector.entity.living.*;
import org.geysermc.connector.entity.living.animal.*;
import org.geysermc.connector.entity.living.animal.tameable.CatEntity;
import org.geysermc.connector.entity.living.animal.tameable.TameableEntity;
import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity;
import org.geysermc.connector.entity.living.animal.horse.ChestedHorseEntity;
import org.geysermc.connector.entity.living.animal.horse.HorseEntity;
import org.geysermc.connector.entity.living.animal.horse.LlamaEntity;
import org.geysermc.connector.entity.living.animal.tameable.WolfEntity;
import org.geysermc.connector.entity.living.animal.horse.*;
import org.geysermc.connector.entity.living.animal.tameable.*;
import org.geysermc.connector.entity.living.merchant.*;
import org.geysermc.connector.entity.living.monster.*;
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity;
import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity;
@ -49,7 +45,7 @@ public enum EntityType {
PIG(PigEntity.class, 12, 0.9f),
SHEEP(SheepEntity.class, 13, 1.3f, 0.9f),
WOLF(WolfEntity.class, 14, 0.85f, 0.6f),
VILLAGER(AbstractMerchantEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f),
VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f),
MOOSHROOM(AnimalEntity.class, 16, 1.4f, 0.9f),
SQUID(WaterEntity.class, 17, 0.8f),
RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f),
@ -64,8 +60,8 @@ public enum EntityType {
ZOMBIE_HORSE(AbstractHorseEntity.class, 27, 1.6f, 1.3965f),
POLAR_BEAR(PolarBearEntity.class, 28, 1.4f, 1.3f),
LLAMA(LlamaEntity.class, 29, 1.87f, 0.9f),
TRADER_LLAMA(LlamaEntity.class, 29, 1.187f, 0.9f),
PARROT(TameableEntity.class, 30, 0.9f, 0.5f),
TRADER_LLAMA(TraderLlamaEntity.class, 29, 1.187f, 0.9f),
PARROT(ParrotEntity.class, 30, 0.9f, 0.5f),
DOLPHIN(WaterEntity.class, 31, 0.6f, 0.9f),
ZOMBIE(ZombieEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f),
CREEPER(CreeperEntity.class, 33, 1.7f, 0.6f, 0.6f, 1.62f),
@ -73,7 +69,7 @@ public enum EntityType {
SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f),
ZOMBIE_PIGMAN(MonsterEntity.class, 36, 1.8f, 0.6f, 0.6f, 1.62f),
SLIME(InsentientEntity.class, 37, 0.51f),
ENDERMAN(MonsterEntity.class, 38, 2.9f, 0.6f),
ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f),
SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f),
CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f),
GHAST(FlyingEntity.class, 41, 4.0f),
@ -88,8 +84,8 @@ public enum EntityType {
ELDER_GUARDIAN(GuardianEntity.class, 50, 1.9975f),
NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f),
WITHER(MonsterEntity.class, 52, 3.5f, 0.9f),
ENDER_DRAGON(InsentientEntity.class, 53, 4f, 13f),
SHULKER(GolemEntity.class, 54, 1f, 1f),
ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f),
SHULKER(ShulkerEntity.class, 54, 1f, 1f),
ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f),
AGENT(Entity.class, 56, 0f),
VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f),
@ -103,18 +99,18 @@ public enum EntityType {
PLAYER(PlayerEntity.class, 63, 1.8f, 0.6f, 0.6f, 1.62f),
ITEM(ItemEntity.class, 64, 0.25f, 0.25f),
TNT(Entity.class, 65, 0.98f, 0.98f),
FALLING_BLOCK(Entity.class, 66, 0.98f, 0.98f),
FALLING_BLOCK(FallingBlockEntity.class, 66, 0.98f, 0.98f),
MOVING_BLOCK(Entity.class, 67, 0f),
EXPERIENCE_BOTTLE(ThrowableEntity.class, 68, 0.25f, 0.25f),
EXPERIENCE_ORB(ExpOrbEntity.class, 69, 0f),
EYE_OF_ENDER(Entity.class, 70, 0f),
END_CRYSTAL(Entity.class, 71, 0f),
END_CRYSTAL(EnderCrystalEntity.class, 71, 0f),
FIREWORK_ROCKET(Entity.class, 72, 0f),
TRIDENT(ArrowEntity.class, 73, 0f),
TURTLE(AnimalEntity.class, 74, 0.4f, 1.2f),
CAT(CatEntity.class, 75, 0.35f, 0.3f),
SHULKER_BULLET(Entity.class, 76, 0f),
FISHING_BOBBER(Entity.class, 77, 0f),
FISHING_BOBBER(FishingHookEntity.class, 77, 0f),
CHALKBOARD(Entity.class, 78, 0f),
DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 0f),
ARROW(ArrowEntity.class, 80, 0.25f, 0.25f),

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 GeyserMC. http://geysermc.org
* 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 GeyserMC. http://geysermc.org
* 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

View file

@ -1,107 +0,0 @@
package org.geysermc.connector.metrics;
import io.sentry.Sentry;
import io.sentry.SentryClient;
import io.sentry.SentryClientFactory;
import io.sentry.context.Context;
import io.sentry.event.BreadcrumbBuilder;
import io.sentry.event.UserBuilder;
public class SentryMetrics {
private static SentryClient sentry;
public static void init() {
/*
It is recommended that you use the DSN detection system, which
will check the environment variable "SENTRY_DSN", the Java
System Property "sentry.dsn", or the "sentry.properties" file
in your classpath. This makes it easier to provide and adjust
your DSN without needing to change your code. See the configuration
page for more information.
*/
Sentry.init();
// You can also manually provide the DSN to the ``init`` method.
Sentry.init();
/*
It is possible to go around the static ``Sentry`` API, which means
you are responsible for making the SentryClient instance available
to your code.
*/
sentry = SentryClientFactory.sentryClient();
SentryMetrics metrics = new SentryMetrics();
metrics.logWithStaticAPI();
metrics.logWithInstanceAPI();
}
/**
* An example method that throws an exception.
*/
void unsafeMethod() {
throw new UnsupportedOperationException("You shouldn't call this!");
}
/**
* Examples using the (recommended) static API.
*/
void logWithStaticAPI() {
// Note that all fields set on the context are optional. Context data is copied onto
// all future events in the current context (until the context is cleared).
// Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept.
Sentry.getContext().recordBreadcrumb(
new BreadcrumbBuilder().setMessage("User made an action").build()
);
// Set the user in the current context.
Sentry.getContext().setUser(
new UserBuilder().setEmail("hello@sentry.io").build()
);
// Add extra data to future events in this context.
Sentry.getContext().addExtra("extra", "thing");
// Add an additional tag to future events in this context.
Sentry.getContext().addTag("tagName", "tagValue");
/*
This sends a simple event to Sentry using the statically stored instance
that was created in the ``main`` method.
*/
Sentry.capture("This is a test");
try {
unsafeMethod();
} catch (Exception e) {
// This sends an exception event to Sentry using the statically stored instance
// that was created in the ``main`` method.
Sentry.capture(e);
}
}
/**
* Examples that use the SentryClient instance directly.
*/
void logWithInstanceAPI() {
// Retrieve the current context.
Context context = sentry.getContext();
// Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept.
context.recordBreadcrumb(new BreadcrumbBuilder().setMessage("User made an action").build());
// Set the user in the current context.
context.setUser(new UserBuilder().setEmail("geyser.project@gmail.com").build());
// This sends a simple event to Sentry.
sentry.sendMessage("This is a test");
try {
unsafeMethod();
} catch (Exception e) {
// This sends an exception event to Sentry.
sentry.sendException(e);
}
}
}

View file

@ -80,6 +80,13 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
pong.setMotd(config.getBedrock().getMotd1());
pong.setMotd(config.getBedrock().getMotd2());
}
//Bedrock will not even attempt a connection if the client thinks the server is full
//so we have to fake it not being full
if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) {
pong.setMaximumPlayerCount(pong.getPlayerCount() + 1);
}
return pong;
}

View file

@ -27,6 +27,7 @@ package org.geysermc.connector.network;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.common.AuthType;
import org.geysermc.common.IGeyserConfiguration;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
@ -85,7 +86,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(ModalFormResponsePacket packet) {
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormData());
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
}
private boolean couldLoginUserByName(String bedrockUsername) {
@ -107,7 +108,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(MovePlayerPacket packet) {
if (!session.isLoggedIn() && !session.isLoggingIn()) {
if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) {
// TODO it is safer to key authentication on something that won't change (UUID, not username)
if (!couldLoginUserByName(session.getAuthData().getName())) {
LoginEncryptionUtils.showLoginWindow(session);

View file

@ -26,6 +26,7 @@
package org.geysermc.connector.network.session;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
import com.github.steveice10.mc.auth.exception.request.RequestException;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
@ -38,6 +39,7 @@ import com.github.steveice10.packetlib.tcp.TcpSessionFactory;
import com.nukkitx.math.GenericMath;
import com.nukkitx.math.TrigMath;
import com.nukkitx.math.vector.Vector2f;
import com.nukkitx.math.vector.Vector2i;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.tag.CompoundTag;
@ -63,6 +65,7 @@ import org.geysermc.connector.network.session.cache.*;
import org.geysermc.connector.network.translators.Registry;
import org.geysermc.connector.network.translators.block.BlockTranslator;
import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.utils.Toolbox;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.EncryptionUtil;
@ -96,6 +99,8 @@ public class GeyserSession implements CommandSender {
private DataCache<Packet> javaPacketCache;
@Setter
private Vector2i lastChunkPosition = null;
private int renderDistance;
private boolean loggedIn;
@ -144,15 +149,6 @@ public class GeyserSession implements CommandSender {
public void connect(RemoteServer remoteServer) {
startGame();
this.remoteServer = remoteServer;
if (connector.getAuthType() != AuthType.ONLINE) {
connector.getLogger().info(
"Attempting to login using " + connector.getAuthType().name().toLowerCase() + " mode... " +
(connector.getAuthType() == AuthType.OFFLINE ?
"authentication is disabled." : "authentication will be encrypted"
)
);
authenticate(authData.getName());
}
ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false);
@ -169,6 +165,18 @@ public class GeyserSession implements CommandSender {
upstream.sendPacket(playStatusPacket);
}
public void login() {
if (connector.getAuthType() != AuthType.ONLINE) {
connector.getLogger().info(
"Attempting to login using " + connector.getAuthType().name().toLowerCase() + " mode... " +
(connector.getAuthType() == AuthType.OFFLINE ?
"authentication is disabled." : "authentication will be encrypted"
)
);
authenticate(authData.getName());
}
}
public void authenticate(String username) {
authenticate(username, "");
}
@ -179,7 +187,7 @@ public class GeyserSession implements CommandSender {
return;
}
loggedIn = true;
loggingIn = true;
// new thread so clients don't timeout
new Thread(() -> {
try {
@ -248,6 +256,17 @@ public class GeyserSession implements CommandSender {
connector.getLogger().info(authData.getName() + " (logged in as: " + protocol.getProfile().getName() + ")" + " has connected to remote java server on address " + remoteServer.getAddress());
playerEntity.setUuid(protocol.getProfile().getId());
playerEntity.setUsername(protocol.getProfile().getName());
String locale = clientData.getLanguageCode();
// Let the user know there locale may take some time to download
// as it has to be extracted from a JAR
if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
sendMessage("Downloading your locale (en_us) this may take some time");
}
// Download and load the language for the player
LocaleUtils.downloadAndLoadLocale(locale);
}
@Override
@ -255,6 +274,9 @@ public class GeyserSession implements CommandSender {
loggingIn = false;
loggedIn = false;
connector.getLogger().info(authData.getName() + " has disconnected from remote java server on address " + remoteServer.getAddress() + " because of " + event.getReason());
if (event.getCause() != null) {
event.getCause().printStackTrace();
}
upstream.disconnect(event.getReason());
}
@ -278,6 +300,9 @@ public class GeyserSession implements CommandSender {
downstream.getSession().connect();
connector.addPlayer(this);
} catch (InvalidCredentialsException | IllegalArgumentException e) {
connector.getLogger().info("User '" + username + "' entered invalid login info, kicking.");
disconnect("Invalid/incorrect login info");
} catch (RequestException ex) {
ex.printStackTrace();
}

View file

@ -0,0 +1,121 @@
/*
* 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.message.Message;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import com.nukkitx.protocol.bedrock.packet.BossEventPacket;
import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
import lombok.AllArgsConstructor;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.MessageUtils;
@AllArgsConstructor
public class BossBar {
private GeyserSession session;
private long entityId;
private Message title;
private float health;
private int color;
private int overlay;
private int darkenSky;
public void addBossBar() {
addBossEntity();
updateBossBar();
}
public void updateBossBar() {
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.SHOW);
bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode()));
bossEventPacket.setHealthPercentage(health);
bossEventPacket.setColor(color); //ignored by client
bossEventPacket.setOverlay(overlay);
bossEventPacket.setDarkenSky(darkenSky);
session.getUpstream().sendPacket(bossEventPacket);
}
public void updateTitle(Message title) {
this.title = title;
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.TITLE);
bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode()));
session.getUpstream().sendPacket(bossEventPacket);
}
public void updateHealth(float health) {
this.health = health;
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE);
bossEventPacket.setHealthPercentage(health);
session.getUpstream().sendPacket(bossEventPacket);
}
public void removeBossBar() {
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.HIDE);
session.getUpstream().sendPacket(bossEventPacket);
removeBossEntity();
}
/**
* Bedrock still needs an entity to display the BossBar.<br>
* Just like 1.8 but it doesn't care about which entity
*/
private void addBossEntity() {
AddEntityPacket addEntityPacket = new AddEntityPacket();
addEntityPacket.setUniqueEntityId(entityId);
addEntityPacket.setRuntimeEntityId(entityId);
addEntityPacket.setIdentifier("minecraft:creeper");
addEntityPacket.setEntityType(33);
addEntityPacket.setPosition(session.getPlayerEntity().getPosition());
addEntityPacket.setRotation(Vector3f.ZERO);
addEntityPacket.setMotion(Vector3f.ZERO);
addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work?
session.getUpstream().sendPacket(addEntityPacket);
}
private void removeBossEntity() {
RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket();
removeEntityPacket.setUniqueEntityId(entityId);
session.getUpstream().sendPacket(removeEntityPacket);
}
}

View file

@ -48,7 +48,7 @@ public class EntityCache {
private Long2ObjectMap<Entity> entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
private Map<UUID, PlayerEntity> playerEntities = Collections.synchronizedMap(new HashMap<>());
private Object2LongMap<UUID> bossbars = new Object2LongOpenHashMap<>();
private Map<UUID, BossBar> bossBars = Collections.synchronizedMap(new HashMap<>());
@Getter
private AtomicLong nextEntityId = new AtomicLong(2L);
@ -116,24 +116,30 @@ public class EntityCache {
playerEntities.remove(uuid);
}
public long addBossBar(UUID uuid) {
long entityId = getNextEntityId().incrementAndGet();
bossbars.put(uuid, entityId);
return entityId;
public void addBossBar(UUID uuid, BossBar bossBar) {
bossBars.put(uuid, bossBar);
bossBar.addBossBar();
}
public long getBossBar(UUID uuid) {
return bossbars.containsKey(uuid) ? bossbars.get(uuid) : -1;
public BossBar getBossBar(UUID uuid) {
return bossBars.get(uuid);
}
public long removeBossBar(UUID uuid) {
return bossbars.remove(uuid);
public void removeBossBar(UUID uuid) {
BossBar bossBar = bossBars.remove(uuid);
if (bossBar != null) {
bossBar.removeBossBar();
}
}
public void updateBossBars() {
bossBars.values().forEach(BossBar::updateBossBar);
}
public void clear() {
entities = null;
entityIdTranslations = null;
playerEntities = null;
bossbars = null;
bossBars = null;
}
}

View file

@ -55,6 +55,8 @@ public class Registry<T> {
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

@ -33,12 +33,10 @@ import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket;
@ -101,10 +99,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
session.getDownstream().getSession().send(stopSleepingPacket);
break;
case BLOCK_INTERACT:
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(position,
BlockFace.values()[packet.getFace()],
Hand.MAIN_HAND, 0, 0, 0, false);
session.getDownstream().getSession().send(blockPacket);
// Handled in BedrockInventoryTransactionTranslator
break;
case START_BREAK:
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
@ -128,6 +123,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.getUpstream().sendPacket(spawnPacket);
entity.updateBedrockAttributes(session);
session.getEntityCache().updateBossBars();
}
break;
case JUMP:

View file

@ -33,15 +33,26 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerSwingArmPacket;
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
import java.util.concurrent.TimeUnit;
@Translator(packet = AnimatePacket.class)
public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
@Override
public void translate(AnimatePacket packet, GeyserSession session) {
// Stop the player sending animations before they have fully spawned into the server
if (!session.isSpawned()) {
return;
}
switch (packet.getAction()) {
case SWING_ARM:
ClientPlayerSwingArmPacket swingArmPacket = new ClientPlayerSwingArmPacket(Hand.MAIN_HAND);
session.getDownstream().getSession().send(swingArmPacket);
// Delay so entity damage can be processed first
session.getConnector().getGeneralThreadPool().schedule(() ->
session.getDownstream().getSession().send(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)),
25,
TimeUnit.MILLISECONDS
);
break;
}
}

View file

@ -34,6 +34,7 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket;
import org.geysermc.connector.utils.MessageUtils;
@Translator(packet = CommandRequestPacket.class)
public class BedrockCommandRequestTranslator extends PacketTranslator<CommandRequestPacket> {
@ -45,7 +46,13 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
if (session.getConnector().getPlatformType() == PlatformType.STANDALONE && command.startsWith("geyser ") && commandMap.getCommands().containsKey(command.split(" ")[1])) {
commandMap.runCommand(session, command);
} else {
ClientChatPacket chatPacket = new ClientChatPacket(packet.getCommand());
String message = packet.getCommand().trim();
if (MessageUtils.isTooLong(message, session)) {
return;
}
ClientChatPacket chatPacket = new ClientChatPacket(message);
session.getDownstream().getSession().send(chatPacket);
}
}

View file

@ -25,6 +25,7 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
@ -49,19 +50,33 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
public void translate(InventoryTransactionPacket packet, GeyserSession session) {
switch (packet.getTransactionType()) {
case ITEM_USE:
if (packet.getActionType() == 1) {
ClientPlayerUseItemPacket useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.getDownstream().getSession().send(useItemPacket);
} else if (packet.getActionType() == 2) {
PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING;
Position pos = new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
ClientPlayerActionPacket breakPacket = new ClientPlayerActionPacket(action, pos, BlockFace.values()[packet.getFace()]);
session.getDownstream().getSession().send(breakPacket);
switch (packet.getActionType()) {
case 0:
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
BlockFace.values()[packet.getFace()],
Hand.MAIN_HAND,
packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(),
false);
session.getDownstream().getSession().send(blockPacket);
break;
case 1:
ClientPlayerUseItemPacket useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.getDownstream().getSession().send(useItemPacket);
break;
case 2:
PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING;
Position pos = new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
ClientPlayerActionPacket breakPacket = new ClientPlayerActionPacket(action, pos, BlockFace.values()[packet.getFace()]);
session.getDownstream().getSession().send(breakPacket);
break;
}
break;
case ITEM_RELEASE:
if (packet.getActionType() == 0) {
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0, 0, 0), BlockFace.DOWN);
// Followed to the Minecraft Protocol specification outlined at wiki.vg
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0,0,0),
BlockFace.DOWN);
session.getDownstream().getSession().send(releaseItemPacket);
}
break;
@ -70,11 +85,23 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
if (entity == null)
return;
Vector3f vector = packet.getClickPosition();
ClientPlayerInteractEntityPacket entityPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
InteractAction.values()[packet.getActionType()], vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND);
session.getDownstream().getSession().send(entityPacket);
//https://wiki.vg/Protocol#Interact_Entity
switch (packet.getActionType()) {
case 0: //Interact
Vector3f vector = packet.getClickPosition();
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
InteractAction.INTERACT, Hand.MAIN_HAND);
ClientPlayerInteractEntityPacket interactAtPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND);
session.getDownstream().getSession().send(interactPacket);
session.getDownstream().getSession().send(interactAtPacket);
break;
case 1: //Attack
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
InteractAction.ATTACK);
session.getDownstream().getSession().send(attackPacket);
break;
}
break;
}
}

View file

@ -34,12 +34,13 @@ import org.geysermc.connector.utils.SkinUtils;
import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
@Translator(packet = SetLocalPlayerAsInitializedPacket.class)
public class BedrockPlayerInitializedTranslator extends PacketTranslator<SetLocalPlayerAsInitializedPacket> {
public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslator<SetLocalPlayerAsInitializedPacket> {
@Override
public void translate(SetLocalPlayerAsInitializedPacket packet, GeyserSession session) {
if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) {
if (!session.getUpstream().isInitialized()) {
session.getUpstream().setInitialized(true);
session.login();
for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) {
if (!entity.isValid()) {

View file

@ -31,6 +31,7 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket;
import org.geysermc.connector.utils.MessageUtils;
@Translator(packet = TextPacket.class)
public class BedrockTextTranslator extends PacketTranslator<TextPacket> {
@ -38,12 +39,24 @@ public class BedrockTextTranslator extends PacketTranslator<TextPacket> {
@Override
public void translate(TextPacket packet, GeyserSession session) {
if (packet.getMessage().charAt(0) == '.') {
ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage().replace(".", "/"));
String message = packet.getMessage().replace(".", "/").trim();
if (MessageUtils.isTooLong(message, session)) {
return;
}
ClientChatPacket chatPacket = new ClientChatPacket(message);
session.getDownstream().getSession().send(chatPacket);
return;
}
ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage());
String message = packet.getMessage().trim();
if (MessageUtils.isTooLong(message, session)) {
return;
}
ClientChatPacket chatPacket = new ClientChatPacket(message);
session.getDownstream().getSession().send(chatPacket);
}
}

View file

@ -55,6 +55,9 @@ public class BlockTranslator {
private static final Int2ObjectMap<BlockState> BEDROCK_TO_JAVA_BLOCK_MAP = new Int2ObjectOpenHashMap<>();
private static final IntSet WATERLOGGED = new IntOpenHashSet();
// Bedrock carpet ID, used in LlamaEntity.java for decoration
public static final int CARPET = 171;
private static final int BLOCK_STATE_VERSION = 17760256;
static {
@ -100,7 +103,8 @@ public class BlockTranslator {
if ("minecraft:water[level=0]".equals(javaId)) {
waterRuntimeId = bedrockRuntimeId;
}
boolean waterlogged = entry.getValue().has("waterlogged") && entry.getValue().get("waterlogged").booleanValue();
boolean waterlogged = entry.getKey().contains("waterlogged=true")
|| javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass");
if (waterlogged) {
BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, new BlockState(javaRuntimeId));
@ -177,6 +181,10 @@ public class BlockTranslator {
return JAVA_TO_BEDROCK_BLOCK_MAP.get(state.getId());
}
public static int getBedrockBlockId(int javaId) {
return JAVA_TO_BEDROCK_BLOCK_MAP.get(javaId);
}
public static BlockState getJavaBlockState(int bedrockId) {
return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId);
}

View file

@ -72,7 +72,18 @@ public class ItemTranslator {
if (stack.getNbt() == null) {
return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount());
}
return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount(), translateToBedrockNBT(stack.getNbt()));
// TODO: Create proper transformers instead of shoving everything here
CompoundTag tag = stack.getNbt();
IntTag mapId = tag.get("map");
if (mapId != null) {
tag.put(new StringTag("map_uuid", mapId.getValue().toString()));
tag.put(new IntTag("map_name_index", mapId.getValue()));
}
return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount(), translateToBedrockNBT(tag));
}
public ItemEntry getItem(ItemStack stack) {

View file

@ -26,80 +26,38 @@
package org.geysermc.connector.network.translators.java;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.BossBar;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.MessageUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerBossBarPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import com.nukkitx.protocol.bedrock.packet.BossEventPacket;
import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
import java.awt.*;
@Translator(packet = ServerBossBarPacket.class)
public class JavaBossBarTranslator extends PacketTranslator<ServerBossBarPacket> {
@Override
public void translate(ServerBossBarPacket packet, GeyserSession session) {
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(session.getEntityCache().getBossBar(packet.getUuid()));
BossBar bossBar = session.getEntityCache().getBossBar(packet.getUuid());
switch (packet.getAction()) {
case ADD:
long entityId = session.getEntityCache().addBossBar(packet.getUuid());
addBossEntity(session, entityId);
bossEventPacket.setAction(BossEventPacket.Action.SHOW);
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle()));
bossEventPacket.setHealthPercentage(packet.getHealth());
bossEventPacket.setColor(0); //ignored by client
bossEventPacket.setOverlay(1);
bossEventPacket.setDarkenSky(0);
long entityId = session.getEntityCache().getNextEntityId().incrementAndGet();
bossBar = new BossBar(session, entityId, packet.getTitle(), packet.getHealth(), 0, 1, 0);
session.getEntityCache().addBossBar(packet.getUuid(), bossBar);
break;
case UPDATE_TITLE:
bossEventPacket.setAction(BossEventPacket.Action.TITLE);
bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle()));
if (bossBar != null) bossBar.updateTitle(packet.getTitle());
break;
case UPDATE_HEALTH:
bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE);
bossEventPacket.setHealthPercentage(packet.getHealth());
if (bossBar != null) bossBar.updateHealth(packet.getHealth());
break;
case REMOVE:
bossEventPacket.setAction(BossEventPacket.Action.HIDE);
removeBossEntity(session, session.getEntityCache().removeBossBar(packet.getUuid()));
session.getEntityCache().removeBossBar(packet.getUuid());
break;
case UPDATE_STYLE:
case UPDATE_FLAGS:
//todo
return;
}
session.getUpstream().sendPacket(bossEventPacket);
}
/**
* Bedrock still needs an entity to display the BossBar.<br>
* Just like 1.8 but it doesn't care about which entity
*/
private void addBossEntity(GeyserSession session, long entityId) {
AddEntityPacket addEntityPacket = new AddEntityPacket();
addEntityPacket.setUniqueEntityId(entityId);
addEntityPacket.setRuntimeEntityId(entityId);
addEntityPacket.setIdentifier("minecraft:creeper");
addEntityPacket.setEntityType(33);
addEntityPacket.setPosition(session.getPlayerEntity().getPosition());
addEntityPacket.setRotation(Vector3f.ZERO);
addEntityPacket.setMotion(Vector3f.ZERO);
addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work?
session.getUpstream().sendPacket(addEntityPacket);
}
private void removeBossEntity(GeyserSession session, long entityId) {
RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket();
removeEntityPacket.setUniqueEntityId(entityId);
session.getUpstream().sendPacket(removeEntityPacket);
}
}

View file

@ -34,6 +34,8 @@ import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket;
import java.util.List;
@Translator(packet = ServerChatPacket.class)
public class JavaChatTranslator extends PacketTranslator<ServerChatPacket> {
@ -58,14 +60,20 @@ public class JavaChatTranslator extends PacketTranslator<ServerChatPacket> {
break;
}
String locale = session.getClientData().getLanguageCode();
if (packet.getMessage() instanceof TranslationMessage) {
textPacket.setType(TextPacket.Type.TRANSLATION);
textPacket.setNeedsTranslation(true);
textPacket.setParameters(MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams()));
textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage()));
List<String> paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams(), locale);
textPacket.setParameters(paramsTranslated);
textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true), paramsTranslated));
} else {
textPacket.setNeedsTranslation(false);
textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage()));
textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false));
}
session.getUpstream().sendPacket(textPacket);

View file

@ -29,7 +29,6 @@ import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.utils.DimensionUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket;
@ -69,7 +68,6 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
session.setRenderDistance(packet.getViewDistance());
if (DimensionUtils.javaToBedrock(packet.getDimension()) != entity.getDimension()) {
ChunkUtils.sendEmptyChunks(session, entity.getPosition().toInt(), 3, true);
DimensionUtils.switchDimension(session, packet.getDimension());
}
}

View file

@ -33,14 +33,56 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket;
import java.nio.charset.StandardCharsets;
@Translator(packet = ServerPluginMessagePacket.class)
public class JavaPluginMessageTranslator extends PacketTranslator<ServerPluginMessagePacket> {
private static byte[] brandData;
static {
byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8);
byte[] varInt = writeVarInt(data.length);
brandData = new byte[varInt.length + data.length];
System.arraycopy(varInt, 0, brandData, 0, varInt.length);
System.arraycopy(data, 0, brandData, varInt.length, data.length);
}
@Override
public void translate(ServerPluginMessagePacket packet, GeyserSession session) {
if (packet.getChannel().equals("minecraft:brand")) {
session.getDownstream().getSession().send(
new ClientPluginMessagePacket(packet.getChannel(), GeyserConnector.NAME.getBytes())
new ClientPluginMessagePacket(packet.getChannel(), brandData)
);
}
}
private static byte[] writeVarInt(int value) {
byte[] data = new byte[getVarIntLength(value)];
int index = 0;
do {
byte temp = (byte)(value & 0b01111111);
value >>>= 7;
if (value != 0) {
temp |= 0b10000000;
}
data[index] = temp;
index++;
} while (value != 0);
return data;
}
private static int getVarIntLength(int number) {
if ((number & 0xFFFFFF80) == 0) {
return 1;
} else if ((number & 0xFFFFC000) == 0) {
return 2;
} else if ((number & 0xFFE00000) == 0) {
return 3;
} else if ((number & 0xF0000000) == 0) {
return 4;
}
return 5;
}
}

View file

@ -25,6 +25,9 @@
package org.geysermc.connector.network.translators.java;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.network.session.GeyserSession;
@ -35,6 +38,8 @@ import org.geysermc.connector.utils.DimensionUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket;
import java.util.concurrent.ThreadLocalRandom;
@Translator(packet = ServerRespawnPacket.class)
public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket> {
@ -53,6 +58,12 @@ public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket>
session.getUpstream().sendPacket(playerGameTypePacket);
session.setGameMode(packet.getGamemode());
LevelEventPacket stopRainPacket = new LevelEventPacket();
stopRainPacket.setType(LevelEventType.STOP_RAIN);
stopRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000);
stopRainPacket.setPosition(Vector3f.ZERO);
session.getUpstream().sendPacket(stopRainPacket);
if (entity.getDimension() != DimensionUtils.javaToBedrock(packet.getDimension())) {
DimensionUtils.switchDimension(session, packet.getDimension());
} else {

View file

@ -0,0 +1,278 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.java;
import com.github.steveice10.mc.protocol.data.game.command.CommandNode;
import com.github.steveice10.mc.protocol.data.game.command.CommandParser;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareCommandsPacket;
import com.nukkitx.protocol.bedrock.data.CommandData;
import com.nukkitx.protocol.bedrock.data.CommandEnumData;
import com.nukkitx.protocol.bedrock.data.CommandParamData;
import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import java.util.*;
@Translator(packet = ServerDeclareCommandsPacket.class)
public class JavaServerDeclareCommandsTranslator extends PacketTranslator<ServerDeclareCommandsPacket> {
@Override
public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) {
List<CommandData> commandData = new ArrayList<>();
Map<Integer, String> commands = new HashMap<>();
Map<Integer, List<CommandNode>> commandArgs = new HashMap<>();
// Get the first node, it should be a root node
CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()];
// Loop through the root nodes to get all commands
for (int nodeIndex : rootNode.getChildIndices()) {
CommandNode node = packet.getNodes()[nodeIndex];
// Make sure we dont have duplicated commands (happens if there is more than 1 root node)
if (commands.containsKey(nodeIndex)) { continue; }
// Get and update the commandArgs list with the found arguments
if (node.getChildIndices().length >= 1) {
for (int childIndex : node.getChildIndices()) {
commandArgs.putIfAbsent(nodeIndex, new ArrayList<>());
commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]);
}
}
// Insert the command name into the list
commands.put(nodeIndex, node.getName());
}
// The command flags, not sure what these do apart from break things
List<CommandData.Flag> flags = new ArrayList<>();
// Loop through all the found commands
for (int commandID : commands.keySet()) {
String commandName = commands.get(commandID);
// Create a basic alias
CommandEnumData aliases = new CommandEnumData( commandName + "Aliases", new String[] { commandName.toLowerCase() }, false);
// Get and parse all params
CommandParamData[][] params = getParams(commandID, packet.getNodes()[commandID], packet.getNodes());
// Build the completed command and add it to the final list
CommandData data = new CommandData(commandName, "", flags, (byte) 0, aliases, params);
commandData.add(data);
}
// Add our commands to the AvailableCommandsPacket for the bedrock client
AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket();
for (CommandData data : commandData) {
availableCommandsPacket.getCommands().add(data);
}
GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands");
// Finally, send the commands to the client
session.getUpstream().sendPacket(availableCommandsPacket);
}
private CommandParamData[][] getParams(int commandID, CommandNode commandNode, CommandNode[] allNodes) {
// Check if the command is an alias and redirect it
if (commandNode.getRedirectIndex() != -1) {
GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName());
commandNode = allNodes[commandNode.getRedirectIndex()];
}
if (commandNode.getChildIndices().length >= 1) {
// Create the root param node and build all the children
ParamInfo rootParam = new ParamInfo(commandNode, null);
rootParam.buildChildren(allNodes);
List<CommandParamData[]> treeData = rootParam.getTree();
CommandParamData[][] params = new CommandParamData[treeData.size()][];
// Fill the nested params array
int i = 0;
for (CommandParamData[] tree : treeData) {
params[i] = tree;
i++;
}
return params;
}
return new CommandParamData[0][0];
}
private CommandParamData.Type mapCommandType(CommandParser parser) {
if (parser == null) { return CommandParamData.Type.STRING; }
switch (parser) {
case FLOAT:
return CommandParamData.Type.FLOAT;
case INTEGER:
return CommandParamData.Type.INT;
case ENTITY:
case GAME_PROFILE:
return CommandParamData.Type.TARGET;
case BLOCK_POS:
return CommandParamData.Type.BLOCK_POSITION;
case COLUMN_POS:
case VEC3:
return CommandParamData.Type.POSITION;
case MESSAGE:
return CommandParamData.Type.MESSAGE;
case NBT:
case NBT_COMPOUND_TAG:
case NBT_TAG:
case NBT_PATH:
return CommandParamData.Type.JSON;
case RESOURCE_LOCATION:
return CommandParamData.Type.FILE_PATH;
case INT_RANGE:
return CommandParamData.Type.INT_RANGE;
case BOOL:
case DOUBLE:
case STRING:
case VEC2:
case BLOCK_STATE:
case BLOCK_PREDICATE:
case ITEM_STACK:
case ITEM_PREDICATE:
case COLOR:
case COMPONENT:
case OBJECTIVE:
case OBJECTIVE_CRITERIA:
case OPERATION: // Possibly OPERATOR
case PARTICLE:
case ROTATION:
case SCOREBOARD_SLOT:
case SCORE_HOLDER:
case SWIZZLE:
case TEAM:
case ITEM_SLOT:
case MOB_EFFECT:
case FUNCTION:
case ENTITY_ANCHOR:
case RANGE:
case FLOAT_RANGE:
case ITEM_ENCHANTMENT:
case ENTITY_SUMMON:
case DIMENSION:
case TIME:
default:
return CommandParamData.Type.STRING;
}
}
@Getter
private class ParamInfo {
private CommandNode paramNode;
private CommandParamData paramData;
private List<ParamInfo> children;
public ParamInfo(CommandNode paramNode, CommandParamData paramData) {
this.paramNode = paramNode;
this.paramData = paramData;
this.children = new ArrayList<>();
}
public void buildChildren(CommandNode[] allNodes) {
int enumIndex = -1;
for (int paramID : paramNode.getChildIndices()) {
CommandNode paramNode = allNodes[paramID];
if (paramNode.getParser() == null) {
if (enumIndex == -1) {
enumIndex = children.size();
// Create the new enum command
CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[] { paramNode.getName() }, false);
children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, enumData, mapCommandType(paramNode.getParser()), null, Collections.emptyList())));
} else {
// Get the existing enum
ParamInfo enumParamInfo = children.get(enumIndex);
// Extend the current list of enum values
String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1);
enumOptions[enumOptions.length - 1] = paramNode.getName();
// Re-create the command using the updated values
CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false);
children.set(enumIndex, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), false, enumData, enumParamInfo.getParamData().getType(), null, Collections.emptyList())));
}
}else{
// Put the non-enum param into the list
children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, null, mapCommandType(paramNode.getParser()), null, Collections.emptyList())));
}
}
// Recursively build all child options
for (ParamInfo child : children) {
child.buildChildren(allNodes);
}
}
public List<CommandParamData[]> getTree() {
List<CommandParamData[]> treeParamData = new ArrayList<>();
for (ParamInfo child : children) {
// Get the tree from the child
List<CommandParamData[]> childTree = child.getTree();
// Un-pack the tree append the child node to it and push into the list
for (CommandParamData[] subchild : childTree) {
CommandParamData[] tmpTree = new ArrayList<CommandParamData>() {
{
add(child.getParamData());
addAll(Arrays.asList(subchild));
}
}.toArray(new CommandParamData[0]);
treeParamData.add(tmpTree);
}
// If we have no more child parameters just the child
if (childTree.size() == 0) {
treeParamData.add(new CommandParamData[] { child.getParamData() });
}
}
return treeParamData;
}
}
}

View file

@ -30,6 +30,7 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.ChunkUtils;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket;
@ -86,6 +87,8 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId());
session.getDownstream().getSession().send(teleportConfirmPacket);
ChunkUtils.updateChunkPosition(session, pos.toInt());
session.getConnector().getLogger().info("Spawned player at " + packet.getX() + " " + packet.getY() + " " + packet.getZ());
return;
}

View file

@ -28,7 +28,9 @@ package org.geysermc.connector.network.translators.java.entity.spawn;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import com.github.steveice10.mc.protocol.data.game.entity.type.object.FallingBlockData;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.FallingBlockEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
@ -59,12 +61,18 @@ public class JavaSpawnObjectTranslator extends PacketTranslator<ServerSpawnObjec
Class<? extends Entity> entityClass = type.getEntityClass();
try {
Constructor<? extends Entity> entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class,
Vector3f.class, Vector3f.class, Vector3f.class);
Entity entity;
if (packet.getType() == ObjectType.FALLING_BLOCK) {
entity = new FallingBlockEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
type, position, motion, rotation, ((FallingBlockData) packet.getData()).getId());
} else {
Constructor<? extends Entity> entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class,
Vector3f.class, Vector3f.class, Vector3f.class);
Entity entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
type, position, motion, rotation
);
entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
type, position, motion, rotation
);
}
session.getEntityCache().spawnEntity(entity);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();

View file

@ -52,12 +52,14 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
case CREATE:
scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers()))
.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix()))
.setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix()));
break;
case UPDATE:
scoreboard.getTeam(packet.getTeamName())
.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix()))
.setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix()))
.setUpdateType(UpdateType.UPDATE);

View file

@ -36,7 +36,6 @@ import org.geysermc.connector.world.chunk.ChunkSection;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.network.VarInts;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
@ -48,63 +47,47 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
@Override
public void translate(ServerChunkDataPacket packet, GeyserSession session) {
if (session.isSpawned()) {
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
}
if (packet.getColumn().getBiomeData() == null) //Non-full chunk
return;
// Not sure if this is safe or not, however without this the client usually times out
GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
try {
if (packet.getColumn().getBiomeData() != null) { //Full chunk
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
ByteBuf byteBuf = Unpooled.buffer(32);
ChunkSection[] sections = chunkData.sections;
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
ByteBuf byteBuf = Unpooled.buffer(32);
ChunkSection[] sections = chunkData.sections;
int sectionCount = sections.length - 1;
while (sectionCount >= 0 && sections[sectionCount].isEmpty()) {
sectionCount--;
}
sectionCount++;
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = chunkData.sections[i];
section.writeToNetwork(byteBuf);
}
byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes
byteBuf.writeByte(0); // Border blocks - Edu edition only
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
byte[] payload = new byte[byteBuf.writerIndex()];
byteBuf.readBytes(payload);
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
levelChunkPacket.setSubChunksLength(sectionCount);
levelChunkPacket.setCachingEnabled(false);
levelChunkPacket.setChunkX(packet.getColumn().getX());
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
levelChunkPacket.setData(payload);
session.getUpstream().sendPacket(levelChunkPacket);
} else {
final int xOffset = packet.getColumn().getX() << 4;
final int zOffset = packet.getColumn().getZ() << 4;
Chunk[] chunks = packet.getColumn().getChunks();
for (int i = 0; i < chunks.length; i++) {
Chunk chunk = chunks[i];
if (chunk == null) continue;
final int yOffset = i * 16;
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
BlockState blockState = chunk.get(x, y, z);
Vector3i pos = Vector3i.from(
x + xOffset,
y + yOffset,
z + zOffset);
ChunkUtils.updateBlock(session, blockState, pos);
}
}
}
}
int sectionCount = sections.length - 1;
while (sectionCount >= 0 && sections[sectionCount].isEmpty()) {
sectionCount--;
}
sectionCount++;
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = chunkData.sections[i];
section.writeToNetwork(byteBuf);
}
byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes
byteBuf.writeByte(0); // Border blocks - Edu edition only
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
byte[] payload = new byte[byteBuf.writerIndex()];
byteBuf.readBytes(payload);
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
levelChunkPacket.setSubChunksLength(sectionCount);
levelChunkPacket.setCachingEnabled(false);
levelChunkPacket.setChunkX(packet.getColumn().getX());
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
levelChunkPacket.setData(payload);
session.getUpstream().sendPacket(levelChunkPacket);
} catch (Exception ex) {
ex.printStackTrace();
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityCollectItemPacket;
import com.nukkitx.protocol.bedrock.packet.TakeItemEntityPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@Translator(packet = ServerEntityCollectItemPacket.class)
public class JavaCollectItemTranslator extends PacketTranslator<ServerEntityCollectItemPacket> {
@Override
public void translate(ServerEntityCollectItemPacket packet, GeyserSession session) {
// This is the definition of translating - both packets take the same values
TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket();
// Collected entity is the item
Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId());
// Collector is the entity picking up the item
Entity collectorEntity;
if (packet.getCollectorEntityId() == session.getPlayerEntity().getEntityId()) {
collectorEntity = session.getPlayerEntity();
} else {
collectorEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectorEntityId());
}
takeItemEntityPacket.setRuntimeEntityId(collectorEntity.getGeyserId());
takeItemEntityPacket.setItemRuntimeEntityId(collectedEntity.getGeyserId());
session.getUpstream().sendPacket(takeItemEntityPacket);
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.data.game.world.map.MapData;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMapDataPacket;
import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.MapColor;
@Translator(packet = ServerMapDataPacket.class)
public class JavaMapDataTranslator extends PacketTranslator<ServerMapDataPacket> {
@Override
public void translate(ServerMapDataPacket packet, GeyserSession session) {
ClientboundMapItemDataPacket mapItemDataPacket = new ClientboundMapItemDataPacket();
mapItemDataPacket.setUniqueMapId(packet.getMapId());
mapItemDataPacket.setDimensionId(session.getPlayerEntity().getDimension());
mapItemDataPacket.setLocked(packet.isLocked());
mapItemDataPacket.setScale(packet.getScale());
MapData data = packet.getData();
if (data != null) {
mapItemDataPacket.setXOffset(data.getX());
mapItemDataPacket.setYOffset(data.getY());
mapItemDataPacket.setWidth(data.getColumns());
mapItemDataPacket.setHeight(data.getRows());
// Every int entry is an ARGB color
int[] colors = new int[data.getData().length];
int idx = 0;
for (byte colorId : data.getData()) {
colors[idx] = MapColor.fromId(colorId).toARGB();
idx++;
}
mapItemDataPacket.setColors(colors);
}
session.getUpstream().getSession().sendPacket(mapItemDataPacket);
}
}

View file

@ -25,6 +25,10 @@
package org.geysermc.connector.network.translators.java.world;
import com.nukkitx.protocol.bedrock.data.GameRuleData;
import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket;
import it.unimi.dsi.fastutil.longs.Long2LongMap;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -35,11 +39,37 @@ import com.nukkitx.protocol.bedrock.packet.SetTimePacket;
@Translator(packet = ServerUpdateTimePacket.class)
public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimePacket> {
// If negative, the last time is stored so we know it's not some plugin behavior doing weird things.
// Per-player for multi-world support
private static final Long2LongMap LAST_RECORDED_TIMES = new Long2LongOpenHashMap();
@Override
public void translate(ServerUpdateTimePacket packet, GeyserSession session) {
// https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day
SetTimePacket setTimePacket = new SetTimePacket();
setTimePacket.setTime((int) Math.abs(packet.getTime()) % 24000);
session.getUpstream().sendPacket(setTimePacket);
// Bedrock sends a GameRulesChangedPacket if there is no daylight cycle
// Java just sends a negative long if there is no daylight cycle
long lastTime = LAST_RECORDED_TIMES.getOrDefault(session.getPlayerEntity().getEntityId(), 0);
long time = packet.getTime();
if (lastTime != time) {
// https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day
SetTimePacket setTimePacket = new SetTimePacket();
setTimePacket.setTime((int) Math.abs(time) % 24000);
session.getUpstream().sendPacket(setTimePacket);
// TODO: Performance efficient to always do this?
LAST_RECORDED_TIMES.put(session.getPlayerEntity().getEntityId(), time);
}
if (lastTime < 0 && time >= 0) {
setDoDaylightCycleGamerule(session, true);
} else if (lastTime != time && time < 0) {
setDoDaylightCycleGamerule(session, false);
}
}
private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
gameRulesChangedPacket.getGameRules().add(new GameRuleData<>("dodaylightcycle", doCycle));
session.getUpstream().sendPacket(gameRulesChangedPacket);
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.java.world;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.ChunkUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateViewPositionPacket;
import com.nukkitx.math.vector.Vector3i;
@ -38,9 +39,8 @@ public class JavaUpdateViewPositionTranslator extends PacketTranslator<ServerUpd
@Override
public void translate(ServerUpdateViewPositionPacket packet, GeyserSession session) {
NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket();
chunkPublisherUpdatePacket.setPosition(Vector3i.from(packet.getChunkX() << 4, 0, packet.getChunkZ() << 4));
chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4);
session.getUpstream().sendPacket(chunkPublisherUpdatePacket);
if (!session.isSpawned() && session.getLastChunkPosition() == null) {
ChunkUtils.updateChunkPosition(session, Vector3i.from(packet.getChunkX() << 4, 64, packet.getChunkZ() << 4));
}
}
}

View file

@ -25,6 +25,7 @@
package org.geysermc.connector.scoreboard;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
import lombok.Setter;
@ -44,10 +45,10 @@ public class Team {
private UpdateType updateType = UpdateType.ADD;
private String name;
private String prefix;
private TeamColor color;
private String suffix;
private Set<String> entities = new ObjectOpenHashSet<>();
public Team(Scoreboard scoreboard, String id) {
this.scoreboard = scoreboard;
this.id = id;

View file

@ -29,8 +29,10 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.nukkitx.math.vector.Vector2i;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.Translators;
@ -74,6 +76,20 @@ public class ChunkUtils {
return chunkData;
}
public static void updateChunkPosition(GeyserSession session, Vector3i position) {
Vector2i chunkPos = session.getLastChunkPosition();
Vector2i newChunkPos = Vector2i.from(position.getX() >> 4, position.getZ() >> 4);
if (chunkPos == null || !chunkPos.equals(newChunkPos)) {
NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket();
chunkPublisherUpdatePacket.setPosition(position);
chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4);
session.getUpstream().sendPacket(chunkPublisherUpdatePacket);
session.setLastChunkPosition(newChunkPos);
}
}
public static void updateBlock(GeyserSession session, BlockState blockState, Position position) {
Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ());
updateBlock(session, blockState, pos);

View file

@ -52,6 +52,7 @@ public class DimensionUtils {
player.setDimension(bedrockDimension);
player.setPosition(pos.toFloat());
session.setSpawned(false);
session.setLastChunkPosition(null);
//let java server handle portal travel sound
StopSoundPacket stopSoundPacket = new StopSoundPacket();

View file

@ -65,4 +65,23 @@ public class FileUtils {
return file;
}
public static void writeFile(File file, char[] data) throws IOException {
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
for (char c : data) {
fos.write(c);
}
fos.flush();
fos.close();
}
public static void writeFile(String name, char[] data) throws IOException {
writeFile(new File(name), data);
}
}

View file

@ -0,0 +1,316 @@
/*
* 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.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipFile;
public class LocaleUtils {
public static final Map<String, Map<String, String>> LOCALE_MAPPINGS = new HashMap<>();
private static final Map<String, Asset> ASSET_MAP = new HashMap<>();
private static final String DEFAULT_LOCALE = (GeyserConnector.getInstance().getConfig().getDefaultLocale() != null ? GeyserConnector.getInstance().getConfig().getDefaultLocale() : "en_us");
private static String smallestURL = "";
static {
// Create the locales folder
File localesFolder = new File("locales/");
localesFolder.mkdir();
// Download the latest asset list and cache it
generateAssetCache();
downloadAndLoadLocale(DEFAULT_LOCALE);
}
private static void generateAssetCache() {
try {
VersionManifest versionManifest = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class);
String latestInfoURL = "";
for (Version version : versionManifest.getVersions()) {
if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) {
latestInfoURL = version.getUrl();
break;
}
}
if (latestInfoURL.isEmpty()) {
throw new Exception("Unable to get latest Minecraft version");
}
VersionInfo versionInfo = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class);
int currentSize = Integer.MAX_VALUE;
for (VersionDownload download : versionInfo.getDownloads().values()) {
if (download.getUrl().endsWith(".jar") && download.getSize() < currentSize) {
smallestURL = download.getUrl();
currentSize = download.getSize();
}
}
JsonNode assets = Toolbox.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects");
Iterator<Map.Entry<String, JsonNode>> assetIterator = assets.fields();
while (assetIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = assetIterator.next();
Asset asset = Toolbox.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class);
ASSET_MAP.put(entry.getKey(), asset);
}
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().info("Failed to load locale asset cache: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()));
}
}
public static void downloadAndLoadLocale(String locale) {
locale = locale.toLowerCase();
if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) {
GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale);
return;
}
GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale);
downloadLocale(locale);
loadLocale(locale);
}
private static void downloadLocale(String locale) {
File localeFile = new File("locales/" + locale + ".json");
if (localeFile.exists()) {
GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale);
return;
}
// Create the en_us locale
if (locale.equals("en_us")) {
downloadEN_US(localeFile);
return;
}
// Get the hash and download the locale
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, "locales/" + locale + ".json");
}
private static void loadLocale(String locale) {
File localeFile = new File("locales/" + locale + ".json");
// Load the locale
if (localeFile.exists()) {
// Read the localefile
InputStream localeStream;
try {
localeStream = new FileInputStream(localeFile);
} catch (FileNotFoundException e) {
throw new AssertionError("Unable to load locale: " + locale + " (" + e.getMessage() + ")");
}
// Parse the file as json
JsonNode localeObj;
try {
localeObj = Toolbox.JSON_MAPPER.readTree(localeStream);
} catch (Exception e) {
throw new AssertionError("Unable to load Java lang map for " + locale, e);
}
// Parse all the locale fields
Iterator<Map.Entry<String, JsonNode>> localeIterator = localeObj.fields();
Map<String, String> langMap = new HashMap<>();
while (localeIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = localeIterator.next();
langMap.put(entry.getKey(), entry.getValue().asText());
}
// Insert the locale into the mappings
LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap);
} else {
GeyserConnector.getInstance().getLogger().warning("Missing locale file: " + locale);
}
}
private static void downloadEN_US(File localeFile) {
try {
// Let the user know we are downloading the JAR
GeyserConnector.getInstance().getLogger().info("Downloading Minecraft JAR to extract en_us locale, please wait... (this may take some time depending on the speed of your internet connection)");
GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL);
// Download the smallest JAR (client or server)
WebUtils.downloadFile(smallestURL, "tmp_locale.jar");
// Load in the JAR as a zip and extract the file
ZipFile localeJar = new ZipFile("tmp_locale.jar");
InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"));
FileOutputStream outputStream = new FileOutputStream(localeFile);
// Write the file to the locale dir
int data = inputStream.read();
while(data != -1){
outputStream.write(data);
data = inputStream.read();
}
// Flush all changes to disk and cleanup
outputStream.flush();
outputStream.close();
inputStream.close();
localeJar.close();
// Delete the nolonger needed client/server jar
Files.delete(Paths.get("tmp_locale.jar"));
} catch (Exception e) {
throw new AssertionError("Unable to download and extract en_us locale!", e);
}
}
public static String getLocaleString(String messageText, String locale) {
Map<String, String> localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase());
if (localeStrings == null)
localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(DEFAULT_LOCALE);
return localeStrings.getOrDefault(messageText, messageText);
}
public static void init() {
// no-op
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
class VersionManifest {
@JsonProperty("latest")
private LatestVersion latestVersion;
@JsonProperty("versions")
private List<Version> versions;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
class LatestVersion {
@JsonProperty("release")
private String release;
@JsonProperty("snapshot")
private String snapshot;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
class Version {
@JsonProperty("id")
private String id;
@JsonProperty("type")
private String type;
@JsonProperty("url")
private String url;
@JsonProperty("time")
private String time;
@JsonProperty("releaseTime")
private String releaseTime;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
class VersionInfo {
@JsonProperty("id")
private String id;
@JsonProperty("type")
private String type;
@JsonProperty("time")
private String time;
@JsonProperty("releaseTime")
private String releaseTime;
@JsonProperty("assetIndex")
private AssetIndex assetIndex;
@JsonProperty("downloads")
private Map<String, VersionDownload> downloads;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
class VersionDownload {
@JsonProperty("sha1")
private String sha1;
@JsonProperty("size")
private int size;
@JsonProperty("url")
private String url;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
class AssetIndex {
@JsonProperty("id")
private String id;
@JsonProperty("sha1")
private String sha1;
@JsonProperty("size")
private int size;
@JsonProperty("totalSize")
private int totalSize;
@JsonProperty("url")
private String url;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
class Asset {
@JsonProperty("hash")
private String hash;
@JsonProperty("size")
private int size;
}

View file

@ -40,9 +40,12 @@ import net.minidev.json.JSONObject;
import org.geysermc.common.window.CustomFormBuilder;
import org.geysermc.common.window.CustomFormWindow;
import org.geysermc.common.window.FormWindow;
import org.geysermc.common.window.SimpleFormWindow;
import org.geysermc.common.window.button.FormButton;
import org.geysermc.common.window.component.InputComponent;
import org.geysermc.common.window.component.LabelComponent;
import org.geysermc.common.window.response.CustomFormResponse;
import org.geysermc.common.window.response.SimpleFormResponse;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.auth.AuthData;
@ -152,43 +155,60 @@ public class LoginEncryptionUtils {
session.getUpstream().sendPacketImmediately(packet);
}
private static int AUTH_FORM_ID = 1337;
private static int AUTH_FORM_ID = 1336;
private static int AUTH_DETAILS_FORM_ID = 1337;
public static void showLoginWindow(GeyserSession session) {
CustomFormWindow window = new CustomFormBuilder("Login")
.addComponent(new LabelComponent("Minecraft: Java Edition account authentication."))
SimpleFormWindow window = new SimpleFormWindow("Login", "You need a Java Edition account to play on this server.");
window.getButtons().add(new FormButton("Login with Minecraft"));
window.getButtons().add(new FormButton("Disconnect"));
session.sendForm(window, AUTH_FORM_ID);
}
public static void showLoginDetailsWindow(GeyserSession session) {
CustomFormWindow window = new CustomFormBuilder("Login Details")
.addComponent(new LabelComponent("Enter the credentials for your Minecraft: Java Edition account below."))
.addComponent(new InputComponent("Email/Username", "account@geysermc.org", ""))
.addComponent(new InputComponent("Password", "123456", ""))
.build();
session.sendForm(window, AUTH_FORM_ID);
session.sendForm(window, AUTH_DETAILS_FORM_ID);
}
public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, String formData) {
public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
WindowCache windowCache = session.getWindowCache();
if (!windowCache.getWindows().containsKey(AUTH_FORM_ID))
if (!windowCache.getWindows().containsKey(formId))
return false;
FormWindow window = windowCache.getWindows().remove(AUTH_FORM_ID);
window.setResponse(formData.trim());
if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) {
FormWindow window = windowCache.getWindows().remove(formId);
window.setResponse(formData.trim());
if (!session.isLoggedIn()) {
if (window instanceof CustomFormWindow) {
CustomFormWindow customFormWindow = (CustomFormWindow) window;
if (!customFormWindow.getTitle().equals("Login"))
return false;
if (!session.isLoggedIn()) {
if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) {
CustomFormWindow customFormWindow = (CustomFormWindow) window;
CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse();
if (response != null) {
String email = response.getInputResponses().get(2);
String password = response.getInputResponses().get(3);
CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse();
if (response != null) {
String email = response.getInputResponses().get(1);
String password = response.getInputResponses().get(2);
session.authenticate(email, password);
session.authenticate(email, password);
}
// Clear windows so authentication data isn't accidentally cached
windowCache.getWindows().clear();
} else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) {
SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
if(response != null) {
if(response.getClickedButtonId() == 0) {
showLoginDetailsWindow(session);
} else if(response.getClickedButtonId() == 1) {
session.disconnect("Login is required");
}
}
}
// Clear windows so authentication data isn't accidentally cached
windowCache.getWindows().clear();
}
}
return true;

View file

@ -0,0 +1,244 @@
package org.geysermc.connector.utils;
import java.util.Arrays;
public enum MapColor {
COLOR_0(-1, -1, -1),
COLOR_1(-1, -1, -1),
COLOR_2(-1, -1, -1),
COLOR_3(-1, -1, -1),
COLOR_4(89, 125, 39),
COLOR_5(109, 153, 48),
COLOR_6(127, 178, 56),
COLOR_7(67, 94, 29),
COLOR_8(174, 164, 115),
COLOR_9(213, 201, 140),
COLOR_10(247, 233, 163),
COLOR_11(130, 123, 86),
COLOR_12(140, 140, 140),
COLOR_13(171, 171, 171),
COLOR_14(199, 199, 199),
COLOR_15(105, 105, 105),
COLOR_16(180, 0, 0),
COLOR_17(220, 0, 0),
COLOR_18(255, 0, 0),
COLOR_19(135, 0, 0),
COLOR_20(112, 112, 180),
COLOR_21(138, 138, 220),
COLOR_22(160, 160, 255),
COLOR_23(84, 84, 135),
COLOR_24(117, 117, 117),
COLOR_25(144, 144, 144),
COLOR_26(167, 167, 167),
COLOR_27(88, 88, 88),
COLOR_28(0, 87, 0),
COLOR_29(0, 106, 0),
COLOR_30(0, 124, 0),
COLOR_31(0, 65, 0),
COLOR_32(180, 180, 180),
COLOR_33(220, 220, 220),
COLOR_34(255, 255, 255),
COLOR_35(135, 135, 135),
COLOR_36(115, 118, 129),
COLOR_37(141, 144, 158),
COLOR_38(164, 168, 184),
COLOR_39(86, 88, 97),
COLOR_40(106, 76, 54),
COLOR_41(130, 94, 66),
COLOR_42(151, 109, 77),
COLOR_43(79, 57, 40),
COLOR_44(79, 79, 79),
COLOR_45(96, 96, 96),
COLOR_46(112, 112, 112),
COLOR_47(59, 59, 59),
COLOR_48(45, 45, 180),
COLOR_49(55, 55, 220),
COLOR_50(64, 64, 255),
COLOR_51(33, 33, 135),
COLOR_52(100, 84, 50),
COLOR_53(123, 102, 62),
COLOR_54(143, 119, 72),
COLOR_55(75, 63, 38),
COLOR_56(180, 177, 172),
COLOR_57(220, 217, 211),
COLOR_58(255, 252, 245),
COLOR_59(135, 133, 129),
COLOR_60(152, 89, 36),
COLOR_61(186, 109, 44),
COLOR_62(216, 127, 51),
COLOR_63(114, 67, 27),
COLOR_64(125, 53, 152),
COLOR_65(153, 65, 186),
COLOR_66(178, 76, 216),
COLOR_67(94, 40, 114),
COLOR_68(72, 108, 152),
COLOR_69(88, 132, 186),
COLOR_70(102, 153, 216),
COLOR_71(54, 81, 114),
COLOR_72(161, 161, 36),
COLOR_73(197, 197, 44),
COLOR_74(229, 229, 51),
COLOR_75(121, 121, 27),
COLOR_76(89, 144, 17),
COLOR_77(109, 176, 21),
COLOR_78(127, 204, 25),
COLOR_79(67, 108, 13),
COLOR_80(170, 89, 116),
COLOR_81(208, 109, 142),
COLOR_82(242, 127, 165),
COLOR_83(128, 67, 87),
COLOR_84(53, 53, 53),
COLOR_85(65, 65, 65),
COLOR_86(76, 76, 76),
COLOR_87(40, 40, 40),
COLOR_88(108, 108, 108),
COLOR_89(132, 132, 132),
COLOR_90(153, 153, 153),
COLOR_91(81, 81, 81),
COLOR_92(53, 89, 108),
COLOR_93(65, 109, 132),
COLOR_94(76, 127, 153),
COLOR_95(40, 67, 81),
COLOR_96(89, 44, 125),
COLOR_97(109, 54, 153),
COLOR_98(127, 63, 178),
COLOR_99(67, 33, 94),
COLOR_100(36, 53, 125),
COLOR_101(44, 65, 153),
COLOR_102(51, 76, 178),
COLOR_103(27, 40, 94),
COLOR_104(72, 53, 36),
COLOR_105(88, 65, 44),
COLOR_106(102, 76, 51),
COLOR_107(54, 40, 27),
COLOR_108(72, 89, 36),
COLOR_109(88, 109, 44),
COLOR_110(102, 127, 51),
COLOR_111(54, 67, 27),
COLOR_112(108, 36, 36),
COLOR_113(132, 44, 44),
COLOR_114(153, 51, 51),
COLOR_115(81, 27, 27),
COLOR_116(17, 17, 17),
COLOR_117(21, 21, 21),
COLOR_118(25, 25, 25),
COLOR_119(13, 13, 13),
COLOR_120(176, 168, 54),
COLOR_121(215, 205, 66),
COLOR_122(250, 238, 77),
COLOR_123(132, 126, 40),
COLOR_124(64, 154, 150),
COLOR_125(79, 188, 183),
COLOR_126(92, 219, 213),
COLOR_127(48, 115, 112),
COLOR_128(52, 90, 180),
COLOR_129(63, 110, 220),
COLOR_130(74, 128, 255),
COLOR_131(39, 67, 135),
COLOR_132(0, 153, 40),
COLOR_133(0, 187, 50),
COLOR_134(0, 217, 58),
COLOR_135(0, 114, 30),
COLOR_136(91, 60, 34),
COLOR_137(111, 74, 42),
COLOR_138(129, 86, 49),
COLOR_139(68, 45, 25),
COLOR_140(79, 1, 0),
COLOR_141(96, 1, 0),
COLOR_142(112, 2, 0),
COLOR_143(59, 1, 0),
COLOR_144(147, 124, 113),
COLOR_145(180, 152, 138),
COLOR_146(209, 177, 161),
COLOR_147(110, 93, 85),
COLOR_148(112, 57, 25),
COLOR_149(137, 70, 31),
COLOR_150(159, 82, 36),
COLOR_151(84, 43, 19),
COLOR_152(105, 61, 76),
COLOR_153(128, 75, 93),
COLOR_154(149, 87, 108),
COLOR_155(78, 46, 57),
COLOR_156(79, 76, 97),
COLOR_157(96, 93, 119),
COLOR_158(112, 108, 138),
COLOR_159(59, 57, 73),
COLOR_160(131, 93, 25),
COLOR_161(160, 114, 31),
COLOR_162(186, 133, 36),
COLOR_163(98, 70, 19),
COLOR_164(72, 82, 37),
COLOR_165(88, 100, 45),
COLOR_166(103, 117, 53),
COLOR_167(54, 61, 28),
COLOR_168(112, 54, 55),
COLOR_169(138, 66, 67),
COLOR_170(160, 77, 78),
COLOR_171(84, 40, 41),
COLOR_172(40, 28, 24),
COLOR_173(49, 35, 30),
COLOR_174(57, 41, 35),
COLOR_175(30, 21, 18),
COLOR_176(95, 75, 69),
COLOR_177(116, 92, 84),
COLOR_178(135, 107, 98),
COLOR_179(71, 56, 51),
COLOR_180(61, 64, 64),
COLOR_181(75, 79, 79),
COLOR_182(87, 92, 92),
COLOR_183(46, 48, 48),
COLOR_184(86, 51, 62),
COLOR_185(105, 62, 75),
COLOR_186(122, 73, 88),
COLOR_187(64, 38, 46),
COLOR_188(53, 43, 64),
COLOR_189(65, 53, 79),
COLOR_190(76, 62, 92),
COLOR_191(40, 32, 48),
COLOR_192(53, 35, 24),
COLOR_193(65, 43, 30),
COLOR_194(76, 50, 35),
COLOR_195(40, 26, 18),
COLOR_196(53, 57, 29),
COLOR_197(65, 70, 36),
COLOR_198(76, 82, 42),
COLOR_199(40, 43, 22),
COLOR_200(100, 42, 32),
COLOR_201(122, 51, 39),
COLOR_202(142, 60, 46),
COLOR_203(75, 31, 24),
COLOR_204(26, 15, 11),
COLOR_205(31, 18, 13),
COLOR_206(37, 22, 16),
COLOR_207(19, 11, 8);
private final int red;
private final int green;
private final int blue;
MapColor(int red, int green, int blue) {
this.red = red;
this.green = green;
this.blue = blue;
}
int getId() {
return ordinal();
}
public static MapColor fromId(int id) {
return Arrays.stream(values()).filter(color -> color.getId() == id).findFirst().orElse(COLOR_0);
}
public int toARGB() {
int alpha = 255;
if (red == -1 && green == -1 && blue == -1)
alpha = 0; // transparent
long result = red & 0xff;
result |= (green & 0xff) << 8;
result |= (blue & 0xff) << 16;
result |= (alpha & 0xff) << 24;
return (int) (result & 0xFFFFFFFFL);
}
}

View file

@ -25,31 +25,31 @@
package org.geysermc.connector.utils;
import com.github.steveice10.mc.protocol.data.message.ChatColor;
import com.github.steveice10.mc.protocol.data.message.ChatFormat;
import com.github.steveice10.mc.protocol.data.message.Message;
import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import com.github.steveice10.mc.protocol.data.message.*;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MessageUtils {
public static List<String> getTranslationParams(Message[] messages) {
List<String> strings = new ArrayList<String>();
for (int i = 0; i < messages.length; i++) {
if (messages[i] instanceof TranslationMessage) {
TranslationMessage translation = (TranslationMessage) messages[i];
public static List<String> getTranslationParams(Message[] messages, String locale) {
List<String> strings = new ArrayList<>();
for (Message message : messages) {
if (message instanceof TranslationMessage) {
TranslationMessage translation = (TranslationMessage) message;
StringBuilder builder = new StringBuilder("");
builder.append("%");
builder.append(translation.getTranslationKey());
strings.add(builder.toString());
if (locale == null) {
String builder = "%" + translation.getTranslationKey();
strings.add(builder);
}
if (translation.getTranslationKey().equals("commands.gamemode.success.other")) {
strings.add("");
@ -59,47 +59,96 @@ public class MessageUtils {
strings.add(" - no permission or invalid command!");
}
for (int j = 0; j < getTranslationParams(translation.getTranslationParams()).size(); j++) {
strings.add(getTranslationParams(translation.getTranslationParams()).get(j));
List<String> furtherParams = getTranslationParams(translation.getTranslationParams(), locale);
if (locale != null) {
strings.add(insertParams(LocaleUtils.getLocaleString(translation.getTranslationKey(), locale), furtherParams));
}else{
strings.addAll(furtherParams);
}
} else {
StringBuilder builder = new StringBuilder("");
builder.append(getFormat(messages[i].getStyle().getFormats()));
builder.append(getColor(messages[i].getStyle().getColor()));
builder.append(getBedrockMessage(messages[i]));
strings.add(builder.toString());
String builder = getFormat(message.getStyle().getFormats()) +
getColorOrParent(message.getStyle());
builder += getTranslatedBedrockMessage(message, locale, false);
strings.add(builder);
}
}
return strings;
}
public static String getTranslationText(TranslationMessage message) {
StringBuilder builder = new StringBuilder("");
builder.append(getFormat(message.getStyle().getFormats()));
builder.append(getColor(message.getStyle().getColor()));
builder.append("%");
builder.append(message.getTranslationKey());
return builder.toString();
public static List<String> getTranslationParams(Message[] messages) {
return getTranslationParams(messages, null);
}
public static String getBedrockMessage(Message message) {
public static String getTranslationText(TranslationMessage message) {
return getFormat(message.getStyle().getFormats()) + getColorOrParent(message.getStyle())
+ "%" + message.getTranslationKey();
}
public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) {
JsonParser parser = new JsonParser();
if (isMessage(message.getText())) {
JsonObject object = parser.parse(message.getText()).getAsJsonObject();
message = Message.fromJson(formatJson(object));
}
StringBuilder builder = new StringBuilder(message.getText());
String messageText = message.getText();
if (locale != null && shouldTranslate) {
messageText = LocaleUtils.getLocaleString(messageText, locale);
}
StringBuilder builder = new StringBuilder();
builder.append(getFormat(message.getStyle().getFormats()));
builder.append(getColorOrParent(message.getStyle()));
builder.append(messageText);
for (Message msg : message.getExtra()) {
builder.append(getFormat(msg.getStyle().getFormats()));
builder.append(getColor(msg.getStyle().getColor()));
builder.append(getColorOrParent(msg.getStyle()));
if (!(msg.getText() == null)) {
builder.append(getBedrockMessage(msg));
boolean isTranslationMessage = (msg instanceof TranslationMessage);
builder.append(getTranslatedBedrockMessage(msg, locale, isTranslationMessage));
}
}
return builder.toString();
}
public static String getTranslatedBedrockMessage(Message message, String locale) {
return getTranslatedBedrockMessage(message, locale, true);
}
public static String getBedrockMessage(Message message) {
return getTranslatedBedrockMessage(message, null, false);
}
public static String insertParams(String message, List<String> params) {
String newMessage = message;
Pattern p = Pattern.compile("%([1-9])\\$s");
Matcher m = p.matcher(message);
while (m.find()) {
try {
newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s" , params.get(Integer.parseInt(m.group(1)) - 1));
} catch (Exception e) {
// Couldnt find the param to replace
}
}
return builder.toString();
for (String text : params) {
newMessage = newMessage.replaceFirst("%s", text);
}
return newMessage;
}
private static String getColorOrParent(MessageStyle style) {
ChatColor chatColor = style.getColor();
if (chatColor == ChatColor.NONE && style.getParent() != null) {
return getColorOrParent(style.getParent());
}
return getColor(chatColor);
}
private static String getColor(ChatColor color) {
@ -206,7 +255,6 @@ public class MessageUtils {
} catch (Exception ex) {
return false;
}
return true;
}
@ -231,7 +279,30 @@ public class MessageUtils {
formatJson((JsonObject) a.get(i));
}
}
return object;
}
public static String toChatColor(TeamColor teamColor) {
for (ChatColor color : ChatColor.values()) {
if (color.name().equals(teamColor.name())) {
return getColor(color);
}
}
for (ChatFormat format : ChatFormat.values()) {
if (format.name().equals(teamColor.name())) {
return getFormat(Collections.singletonList(format));
}
}
return "";
}
public static boolean isTooLong(String message, GeyserSession session) {
if (message.length() > 256) {
// TODO: Add Geyser localization and translate this based on language
session.sendMessage("Your message is bigger than 256 characters (" + message.length() + ") so it has not been sent.");
return true;
}
return false;
}
}

View file

@ -42,7 +42,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.sound.SoundMap;
import java.io.InputStream;
import java.io.*;
import java.util.*;
public class Toolbox {
@ -54,6 +54,8 @@ public class Toolbox {
public static final Int2ObjectMap<ItemEntry> ITEM_ENTRIES = new Int2ObjectOpenHashMap<>();
public static final Map<String, Map<String, String>> LOCALE_MAPPINGS = new HashMap<>();
static {
/* Load biomes */
InputStream biomestream = GeyserConnector.class.getClassLoader().getResourceAsStream("bedrock/biome_definitions.dat");
@ -105,9 +107,11 @@ public class Toolbox {
entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue()));
itemIndex++;
}
/* Load sound mappings */
// Load sound mappings
SoundMap.get();
// Load the locale data
LocaleUtils.init();
}
public static InputStream getResource(String resource) {

View file

@ -0,0 +1,72 @@
/*
* 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 java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class WebUtils {
public static String getBody(String reqURL) {
URL url = null;
try {
url = new URL(reqURL);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
content.append("\n");
}
in.close();
con.disconnect();
return content.toString();
} catch (Exception e) {
return e.getMessage();
}
}
public static void downloadFile(String reqURL, String fileLocation) {
try {
InputStream in = new URL(reqURL).openStream();
Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
throw new AssertionError("Unable to download and save file: " + fileLocation + " (" + reqURL + ")", e);
}
}
}

View file

@ -57,6 +57,9 @@ general-thread-pool: 32
# OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes
allow-third-party-capes: true
# The default locale if we dont have the one the client requested
default-locale: en_us
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
# about Geyser, such as how many people are online, how many servers are using Geyser,
# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.

@ -1 +1 @@
Subproject commit 278c73449aeeb4064c7513a68f98a49a5f463f0a
Subproject commit efc9db6b7d51bdf145230933ac23b321ac1c132d