From d496d4958e544e7f1e1c3f4984f23c13aefa94a8 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 2 Aug 2019 22:38:09 -0500 Subject: [PATCH] Start work on entity translations --- .../org/geysermc/connector/entity/Entity.java | 135 +++++++++++++++ .../connector/entity/PlayerEntity.java | 96 +++++++++++ .../connector/entity/type/EntityType.java | 160 ++++++++++++++++++ .../network/session/GeyserSession.java | 13 +- .../network/session/cache/EntityCache.java | 72 ++++++++ .../geysermc/connector/utils/EntityUtils.java | 23 +++ 6 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/Entity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java new file mode 100644 index 000000000..66d50fd3a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2019 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.flowpowered.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.Attribute; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityDataDictionary; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@Setter +public class Entity { + + protected long entityId; + protected long geyserId; + + protected int dimension; + + protected Vector3f position; + protected Vector3f motion; + protected Vector3f rotation; + + protected int scale = 1; + protected float yaw; + protected float pitch; + protected boolean shouldMove; + + protected EntityType entityType; + + protected boolean valid; + + protected Map attributes = new HashMap(); + + public Entity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + this.entityId = entityId; + this.geyserId = geyserId; + this.entityType = entityType; + this.position = position; + this.motion = motion; + this.rotation = rotation; + + this.valid = false; + this.shouldMove = false; + } + + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(entityId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(rotation); + addEntityPacket.setEntityType(entityType.getType()); + + session.getUpstream().sendPacket(addEntityPacket); + valid = true; + } + + public void despawnEntity(GeyserSession session) { + if (!valid) + return; + + RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); + removeEntityPacket.setUniqueEntityId(entityId); + session.getUpstream().sendPacket(removeEntityPacket); + } + + public void moveRelative(double relX, double relY, double relZ, float pitch, float yaw) { + if (relX == 0 && relY != 0 && relZ != 0 && yaw != 0 && pitch != 0) + return; + + if (pitch != 0) + this.pitch = pitch; + + if (yaw != 0) + this.yaw = yaw; + + this.position = new Vector3f(position.getX() + relX, position.getX() + relY, position.getX() + relZ); + this.shouldMove = true; + } + + public void moveAbsolute(Vector3f position, float pitch, float yaw) { + if (position.getX() == 0 && position.getX() != 0 && position.getX() != 0 && yaw != 0 && pitch != 0) + return; + + this.position = position; + this.pitch = pitch; + this.yaw = yaw; + this.shouldMove = true; + } + + protected EntityDataDictionary getMetadata() { + EntityDataDictionary dictionary = new EntityDataDictionary(); + dictionary.put(EntityData.NAMETAG, ""); + dictionary.put(EntityData.ENTITY_AGE, 0); + dictionary.put(EntityData.SCALE, 1f); + dictionary.put(EntityData.MAX_AIR, (short) 400); + dictionary.put(EntityData.AIR, (short) 0); + dictionary.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); + dictionary.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); + return dictionary; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java new file mode 100644 index 000000000..8797d20c3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 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.flowpowered.math.vector.Vector3f; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityDataDictionary; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; +import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +@Getter +@Setter +public class PlayerEntity extends Entity { + + // This is the session linked to the player entity, can be null + private GeyserSession session; + + private ItemData hand; + + private ItemData helmet; + private ItemData chestplate; + private ItemData leggings; + private ItemData boots; + + public PlayerEntity(GeyserSession session, long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + + this.session = session; + } + + // TODO: Break this into an EquippableEntity class + public void updateEquipment(GeyserSession session) { + if (hand != null && helmet != null && chestplate != null && leggings != null ) + return; + + MobArmorEquipmentPacket armorEquipmentPacket = new MobArmorEquipmentPacket(); + armorEquipmentPacket.setRuntimeEntityId(geyserId); + armorEquipmentPacket.setHelmet(helmet); + armorEquipmentPacket.setChestplate(chestplate); + armorEquipmentPacket.setLeggings(leggings); + armorEquipmentPacket.setBoots(boots); + + session.getUpstream().sendPacket(armorEquipmentPacket); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); + addPlayerPacket.setUniqueEntityId(geyserId); + addPlayerPacket.setUniqueEntityId(entityId); + addPlayerPacket.setUuid(this.session.getAuthenticationData().getUUID()); + addPlayerPacket.setUsername(this.session.getAuthenticationData().getName()); + addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setPosition(position); + addPlayerPacket.setMotion(motion); + addPlayerPacket.setRotation(rotation); + addPlayerPacket.setHand(hand); + addPlayerPacket.getMetadata().putAll(getMetadata()); + addPlayerPacket.setPlayerFlags(0); + addPlayerPacket.setCommandPermission(0); + addPlayerPacket.setWorldFlags(0); + addPlayerPacket.setPlayerPermission(0); + addPlayerPacket.setCustomFlags(0); + addPlayerPacket.setDeviceId("WIN10"); // TODO: Find this value + session.getUpstream().sendPacket(addPlayerPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java new file mode 100644 index 000000000..5525fb0e9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 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.type; + +import lombok.Getter; + +@Getter +public enum EntityType { + + CHICKEN(10, 0.7f, 0.4f), + COW(11, 1.4f, 0.9f), + PIG(12, 0.9f), + SHEEP(13, 1.3f, 0.9f), + WOLF(14, 0.85f, 0.6f), + VILLAGER(15, 1.8f, 0.6f, 0.6f, 1.62f), + MOOSHROOM(16, 1.4f, 0.9f), + SQUID(17, 0.8f), + RABBIT(18, 0.5f, 0.4f), + BAT(19, 0.9f, 0.5f), + IRON_GOLEM(20, 2.7f, 1.4f), + SNOW_GOLEM(21, 1.9f, 0.7f), + OCELOT(22, 0.35f, 0.3f), + HORSE(23, 1.6f, 1.3965f), + DONKEY(24, 1.6f, 1.3965f), + MULE(25, 1.6f, 1.3965f), + SKELETON_HORSE(26, 1.6f, 1.3965f), + ZOMBIE_HORSE(27, 1.6f, 1.3965f), + POLAR_BEAR(28, 1.4f, 1.3f), + LLAMA(29, 1.87f, 0.9f), + PARROT(30, 0.9f, 0.5f), + DOLPHIN(31, 0f), //TODO + ZOMBIE(32, 1.8f, 0.6f, 0.6f, 1.62f), + CREEPER(33, 1.7f, 0.6f, 0.6f, 1.62f), + SKELETON(34, 1.8f, 0.6f, 0.6f, 1.62f), + SPIDER(35, 0.9f, 1.4f, 1.4f, 1f), + ZOMBIE_PIGMAN(36, 1.8f, 0.6f, 0.6f, 1.62f), + SLIME(37, 0.51f), + ENDERMAN(38, 2.9f, 0.6f), + SILVERFISH(39, 0.3f, 0.4f), + CAVE_SPIDER(40, 0.5f, 0.7f), + GHAST(41, 4.0f), + MAGMA_CUBE(42, 0.51f), + BLAZE(43, 1.8f, 0.6f), + ZOMBIE_VILLAGER(44, 1.8f, 0.6f, 0.6f, 1.62f), + WITCH(45, 1.8f, 0.6f, 0.6f, 1.62f), + STRAY(46, 1.8f, 0.6f, 0.6f, 1.62f), + HUSK(47, 1.8f, 0.6f, 0.6f, 1.62f), + WITHER_SKELETON(48, 2.4f, 0.7f), + GUARDIAN(49, 0.85f), + ELDER_GUARDIAN(50, 1.9975f), + NPC(51, 1.8f, 0.6f, 0.6f, 1.62f), + WITHER(52, 3.5f, 0.9f), + ENDER_DRAGON(53, 4f, 13f), + SHULKER(54, 1f, 1f), + ENDERMITE(55, 0.3f, 0.4f), + AGENT(56, 0f), + VINDICATOR(57, 1.8f, 0.6f, 0.6f, 1.62f), + PILLAGER(114, 1.8f, 0.6f, 0.6f, 1.62f), + WANDERING_VILLAGER(118, 1.8f, 0.6f, 0.6f, 1.62f), + RAVAGER(59, 1.9f, 1.2f), + + ARMOR_STAND(61, 0f), + TRIPOD_CAMERA(62, 0f), + PLAYER(63, 1.8f, 0.6f, 0.6f, 1.62f), + ITEM(64, 0.25f, 0.25f), + PRIMED_TNT(65, 0.98f, 0.98f), + FALLING_BLOCK(66, 0.98f, 0.98f), + MOVING_BLOCK(67, 0f), + EXPERIENCE_BOTTLE(68, 0.25f, 0.25f), + EXPERIENCE_ORB(69, 0f), + EYE_OF_ENDER(70, 0f), + ENDER_CRYSTAL(71, 0f), + FIREWORK_ROCKET(72, 0f), + TRIDENT(73, 0f), + + SHULKER_BULLET(76, 0f), + FISHING_HOOK(77, 0f), + CHALKBOARD(78, 0f), + DRAGON_FIREBALL(79, 0f), + ARROW(80, 0.25f, 0.25f), + SNOWBALL(81, 0f), + EGG(82, 0f), + PAINTING(83, 0f), + MINECART(84, 0f), + LARGE_FIREBALL(85, 0f), + SPLASH_POTION(86, 0f), + ENDER_PEARL(87, 0f), + LEASH_KNOT(88, 0f), + WITHER_SKULL(89, 0f), + BOAT(90, 0.7f, 1.6f, 1.6f, 0.35f), + WITHER_SKULL_DANGEROUS(91, 0f), + LIGHTNING_BOLT(93, 0f), + SMALL_FIREBALL(94, 0f), + AREA_EFFECT_CLOUD(95, 0f), + HOPPER_MINECART(96, 0f), + TNT_MINECART(97, 0f), + CHEST_MINECART(98, 0f), + + COMMAND_BLOCK_MINECART(100, 0f), + LINGERING_POTION(101, 0f), + LLAMA_SPIT(102, 0f), + EVOKER_FANGS(103, 0f), + EVOKER(104, 0f), + VEX(105, 0f), + ICE_BOMB(106, 0f), + BALLOON(107, 0f), //TODO + PUFFERFISH(108, 0.7f, 0.7f), + SALMON(109, 0.5f, 0.7f), + TROPICAL_FISH(111, 0.6f, 0.6f), + COD(112, 0.25f, 0.5f); + + private final int type; + private final float height; + private final float width; + private final float length; + private final float offset; + + EntityType(int type, float height) { + this(type, height, 0f); + } + + EntityType(int type, float height, float width) { + this(type, height, width, width); + } + + EntityType(int type, float height, float width, float length) { + this(type, height, width, length, 0f); + } + + EntityType(int type, float height, float width, float length, float offset) { + this.type = type; + this.height = height; + this.width = width; + this.length = length; + this.offset = offset + 0.00001f; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index b7ca7a2f5..fbc11cb97 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -50,6 +50,9 @@ import org.geysermc.api.RemoteServer; import org.geysermc.api.session.AuthData; import org.geysermc.api.window.FormWindow; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.cache.EntityCache; import org.geysermc.connector.network.session.cache.InventoryCache; import org.geysermc.connector.network.session.cache.ScoreboardCache; import org.geysermc.connector.network.session.cache.WindowCache; @@ -72,6 +75,12 @@ public class GeyserSession implements PlayerSession, Player { @Getter private AuthData authenticationData; + @Getter + private PlayerEntity playerEntity; + + @Getter + private EntityCache entityCache; + @Getter private InventoryCache inventoryCache; @@ -90,9 +99,11 @@ public class GeyserSession implements PlayerSession, Player { this.connector = connector; this.upstream = bedrockServerSession; + this.playerEntity = new PlayerEntity(this, 1, 1, EntityType.PLAYER, new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), new Vector3f(0, 0, 0)); + + this.entityCache = new EntityCache(this); this.inventoryCache = new InventoryCache(this); this.windowCache = new WindowCache(this); - this.scoreboardCache = new ScoreboardCache(this); this.loggedIn = false; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java new file mode 100644 index 000000000..e15d8c526 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 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.flowpowered.math.vector.Vector3f; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnMobPacket; +import lombok.Getter; +import org.geysermc.api.Geyser; +import org.geysermc.connector.console.GeyserLogger; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.EntityUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +// Each session has its own EntityCache in the occasion that an entity packet is sent specifically +// for that player (e.g. seeing vanished players from /vanish) +public class EntityCache { + + private GeyserSession session; + + @Getter + private Map entities = new HashMap(); + + private AtomicLong nextEntityId = new AtomicLong(2L); + + public EntityCache(GeyserSession session) { + this.session = session; + } + + public Entity spawnEntity(ServerSpawnMobPacket packet) { + EntityType type = EntityUtils.toBedrockEntity(packet.getType()); + if (type == null) { + GeyserLogger.DEFAULT.warning("Mob " + packet.getType() + " is not supported yet!"); + return null; + } + + Vector3f position = new Vector3f(packet.getX(), packet.getY(), packet.getZ()); + Vector3f motion = new Vector3f(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ()); + Vector3f rotation = new Vector3f(packet.getPitch(), packet.getYaw(), packet.getHeadYaw()); + + Entity entity = new Entity(packet.getEntityId(), nextEntityId.incrementAndGet(), type, position, motion, rotation); + entity.moveAbsolute(position, packet.getPitch(), packet.getYaw()); + return entities.put(entity.getGeyserId(), entity); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java new file mode 100644 index 000000000..50b616ea5 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java @@ -0,0 +1,23 @@ +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.protocol.data.game.entity.type.MobType; +import org.geysermc.connector.entity.type.EntityType; + +public class EntityUtils { + + public static MobType toJavaEntity(EntityType type) { + try { + return MobType.valueOf(type.name()); + } catch (IllegalArgumentException ex) { + return null; + } + } + + public static EntityType toBedrockEntity(MobType type) { + try { + return EntityType.valueOf(type.name()); + } catch (IllegalArgumentException ex) { + return null; + } + } +}