aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower/net/minecraft/server/level/ServerEntity.java.patch
diff options
context:
space:
mode:
authorMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
committerMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
commitbee74680e607c2e29b038329f62181238911cd83 (patch)
tree708fd1a4a0227d9071243adf2a42d5e9e96cde4a /patch-remap/mache-vineflower/net/minecraft/server/level/ServerEntity.java.patch
parent0a44692ef6ff6e255d48eb3ba1bb114166eafda9 (diff)
downloadPaper-softspoon.tar.gz
Paper-softspoon.zip
add remapped patches as a testsoftspoon
Diffstat (limited to 'patch-remap/mache-vineflower/net/minecraft/server/level/ServerEntity.java.patch')
-rw-r--r--patch-remap/mache-vineflower/net/minecraft/server/level/ServerEntity.java.patch485
1 files changed, 485 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower/net/minecraft/server/level/ServerEntity.java.patch b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerEntity.java.patch
new file mode 100644
index 0000000000..3329263599
--- /dev/null
+++ b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerEntity.java.patch
@@ -0,0 +1,485 @@
+--- a/net/minecraft/server/level/ServerEntity.java
++++ b/net/minecraft/server/level/ServerEntity.java
+@@ -6,7 +6,9 @@
+ import java.util.ArrayList;
+ import java.util.Collection;
+ import java.util.Collections;
++import java.util.Iterator;
+ import java.util.List;
++import java.util.Objects;
+ import java.util.Set;
+ import java.util.function.Consumer;
+ import java.util.stream.Stream;
+@@ -26,7 +28,6 @@
+ import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
+ import net.minecraft.network.protocol.game.VecDeltaCodec;
+ import net.minecraft.network.syncher.SynchedEntityData;
+-import net.minecraft.util.Mth;
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.entity.EquipmentSlot;
+ import net.minecraft.world.entity.LivingEntity;
+@@ -40,10 +41,18 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import net.minecraft.server.network.ServerPlayerConnection;
++import net.minecraft.util.Mth;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerVelocityEvent;
++// CraftBukkit end
++
+ public class ServerEntity {
++
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final int TOLERANCE_LEVEL_ROTATION = 1;
+- private static final double TOLERANCE_LEVEL_POSITION = 7.6293945E-6F;
++ private static final double TOLERANCE_LEVEL_POSITION = 7.62939453125E-6D;
+ public static final int FORCED_POS_UPDATE_PERIOD = 60;
+ private static final int FORCED_TELEPORT_PERIOD = 400;
+ private final ServerLevel level;
+@@ -55,21 +64,27 @@
+ private int yRotp;
+ private int xRotp;
+ private int yHeadRotp;
+- private Vec3 ap = Vec3.ZERO;
++ private Vec3 ap;
+ private int tickCount;
+ private int teleportDelay;
+- private List<Entity> lastPassengers = Collections.emptyList();
++ private List<Entity> lastPassengers;
+ private boolean wasRiding;
+ private boolean wasOnGround;
+ @Nullable
+ private List<SynchedEntityData.DataValue<?>> trackedDataValues;
++ // CraftBukkit start
++ private final Set<ServerPlayerConnection> trackedPlayers;
+
+- public ServerEntity(ServerLevel level, Entity entity, int updateInterval, boolean trackDelta, Consumer<Packet<?>> broadcast) {
+- this.level = level;
+- this.broadcast = broadcast;
++ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, Set<ServerPlayerConnection> trackedPlayers) {
++ this.trackedPlayers = trackedPlayers;
++ // CraftBukkit end
++ this.ap = Vec3.ZERO;
++ this.lastPassengers = Collections.emptyList();
++ this.level = worldserver;
++ this.broadcast = consumer;
+ this.entity = entity;
+- this.updateInterval = updateInterval;
+- this.trackDelta = trackDelta;
++ this.updateInterval = i;
++ this.trackDelta = flag;
+ this.positionCodec.setBase(entity.trackingPosition());
+ this.yRotp = Mth.floor(entity.getYRot() * 256.0F / 360.0F);
+ this.xRotp = Mth.floor(entity.getXRot() * 256.0F / 360.0F);
+@@ -79,149 +94,191 @@
+ }
+
+ public void sendChanges() {
+- List<Entity> passengers = this.entity.getPassengers();
+- if (!passengers.equals(this.lastPassengers)) {
+- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity));
+- removedPassengers(passengers, this.lastPassengers)
+- .forEach(
+- entity -> {
+- if (entity instanceof ServerPlayer serverPlayer1) {
+- serverPlayer1.connection
+- .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot());
+- }
+- }
+- );
+- this.lastPassengers = passengers;
++ List<Entity> list = this.entity.getPassengers();
++
++ if (!list.equals(this.lastPassengers)) {
++ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
++ removedPassengers(list, this.lastPassengers).forEach((entity) -> {
++ if (entity instanceof ServerPlayer) {
++ ServerPlayer entityplayer = (ServerPlayer) entity;
++
++ entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot());
++ }
++
++ });
++ this.lastPassengers = list;
+ }
+
+- if (this.entity instanceof ItemFrame itemFrame && this.tickCount % 10 == 0) {
+- ItemStack item = itemFrame.getItem();
+- if (item.getItem() instanceof MapItem) {
+- Integer mapId = MapItem.getMapId(item);
+- MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level);
+- if (savedData != null) {
+- for (ServerPlayer serverPlayer : this.level.players()) {
+- savedData.tickCarriedBy(serverPlayer, item);
+- Packet<?> updatePacket = savedData.getUpdatePacket(mapId, serverPlayer);
+- if (updatePacket != null) {
+- serverPlayer.connection.send(updatePacket);
++ Entity entity = this.entity;
++
++ if (entity instanceof ItemFrame) {
++ ItemFrame entityitemframe = (ItemFrame) entity;
++
++ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
++ ItemStack itemstack = entityitemframe.getItem();
++
++ if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
++ Integer integer = MapItem.getMapId(itemstack);
++ MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level);
++
++ if (worldmap != null) {
++ Iterator<ServerPlayerConnection> iterator = this.trackedPlayers.iterator(); // CraftBukkit
++
++ while (iterator.hasNext()) {
++ ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit
++
++ worldmap.tickCarriedBy(entityplayer, itemstack);
++ Packet<?> packet = worldmap.getUpdatePacket(integer, entityplayer);
++
++ if (packet != null) {
++ entityplayer.connection.send(packet);
++ }
+ }
+ }
+ }
+- }
+
+- this.sendDirtyEntityData();
++ this.sendDirtyEntityData();
++ }
+ }
+
+ if (this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) {
++ int i;
++ int j;
++
+ if (this.entity.isPassenger()) {
+- int floor = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F);
+- int floor1 = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F);
+- boolean flag = Math.abs(floor - this.yRotp) >= 1 || Math.abs(floor1 - this.xRotp) >= 1;
++ i = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F);
++ j = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F);
++ boolean flag = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1;
++
+ if (flag) {
+- this.broadcast.accept(new ClientboundMoveEntityPacket.Rot(this.entity.getId(), (byte)floor, (byte)floor1, this.entity.onGround()));
+- this.yRotp = floor;
+- this.xRotp = floor1;
++ this.broadcast.accept(new ClientboundMoveEntityPacket.Rot(this.entity.getId(), (byte) i, (byte) j, this.entity.onGround()));
++ this.yRotp = i;
++ this.xRotp = j;
+ }
+
+ this.positionCodec.setBase(this.entity.trackingPosition());
+ this.sendDirtyEntityData();
+ this.wasRiding = true;
+ } else {
+- this.teleportDelay++;
+- int floor = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F);
+- int floor1 = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F);
+- Vec3 vec3 = this.entity.trackingPosition();
+- boolean flag1 = this.positionCodec.delta(vec3).lengthSqr() >= 7.6293945E-6F;
+- Packet<?> packet = null;
++ ++this.teleportDelay;
++ i = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F);
++ j = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F);
++ Vec3 vec3d = this.entity.trackingPosition();
++ boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D;
++ Packet<?> packet1 = null;
+ boolean flag2 = flag1 || this.tickCount % 60 == 0;
+- boolean flag3 = Math.abs(floor - this.yRotp) >= 1 || Math.abs(floor1 - this.xRotp) >= 1;
++ boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1;
+ boolean flag4 = false;
+ boolean flag5 = false;
++
+ if (this.tickCount > 0 || this.entity instanceof AbstractArrow) {
+- long l = this.positionCodec.encodeX(vec3);
+- long l1 = this.positionCodec.encodeY(vec3);
+- long l2 = this.positionCodec.encodeZ(vec3);
+- boolean flag6 = l < -32768L || l > 32767L || l1 < -32768L || l1 > 32767L || l2 < -32768L || l2 > 32767L;
+- if (flag6 || this.teleportDelay > 400 || this.wasRiding || this.wasOnGround != this.entity.onGround()) {
+- this.wasOnGround = this.entity.onGround();
+- this.teleportDelay = 0;
+- packet = new ClientboundTeleportEntityPacket(this.entity);
+- flag4 = true;
+- flag5 = true;
+- } else if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
+- if (flag2) {
+- packet = new ClientboundMoveEntityPacket.Pos(
+- this.entity.getId(), (short)((int)l), (short)((int)l1), (short)((int)l2), this.entity.onGround()
+- );
++ long k = this.positionCodec.encodeX(vec3d);
++ long l = this.positionCodec.encodeY(vec3d);
++ long i1 = this.positionCodec.encodeZ(vec3d);
++ boolean flag6 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
++
++ if (!flag6 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()) {
++ if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
++ if (flag2) {
++ packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.onGround());
++ flag4 = true;
++ } else if (flag3) {
++ packet1 = new ClientboundMoveEntityPacket.Rot(this.entity.getId(), (byte) i, (byte) j, this.entity.onGround());
++ flag5 = true;
++ }
++ } else {
++ packet1 = new ClientboundMoveEntityPacket.PosRot(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), (byte) i, (byte) j, this.entity.onGround());
+ flag4 = true;
+- } else if (flag3) {
+- packet = new ClientboundMoveEntityPacket.Rot(this.entity.getId(), (byte)floor, (byte)floor1, this.entity.onGround());
+ flag5 = true;
+ }
+ } else {
+- packet = new ClientboundMoveEntityPacket.PosRot(
+- this.entity.getId(), (short)((int)l), (short)((int)l1), (short)((int)l2), (byte)floor, (byte)floor1, this.entity.onGround()
+- );
++ this.wasOnGround = this.entity.onGround();
++ this.teleportDelay = 0;
++ packet1 = new ClientboundTeleportEntityPacket(this.entity);
+ flag4 = true;
+ flag5 = true;
+ }
+ }
+
+- if ((this.trackDelta || this.entity.hasImpulse || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying())
+- && this.tickCount > 0) {
+- Vec3 deltaMovement = this.entity.getDeltaMovement();
+- double d = deltaMovement.distanceToSqr(this.ap);
+- if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) {
+- this.ap = deltaMovement;
++ if ((this.trackDelta || this.entity.hasImpulse || this.entity instanceof LivingEntity && ((LivingEntity) this.entity).isFallFlying()) && this.tickCount > 0) {
++ Vec3 vec3d1 = this.entity.getDeltaMovement();
++ double d0 = vec3d1.distanceToSqr(this.ap);
++
++ if (d0 > 1.0E-7D || d0 > 0.0D && vec3d1.lengthSqr() == 0.0D) {
++ this.ap = vec3d1;
+ this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.ap));
+ }
+ }
+
+- if (packet != null) {
+- this.broadcast.accept(packet);
++ if (packet1 != null) {
++ this.broadcast.accept(packet1);
+ }
+
+ this.sendDirtyEntityData();
+ if (flag4) {
+- this.positionCodec.setBase(vec3);
++ this.positionCodec.setBase(vec3d);
+ }
+
+ if (flag5) {
+- this.yRotp = floor;
+- this.xRotp = floor1;
++ this.yRotp = i;
++ this.xRotp = j;
+ }
+
+ this.wasRiding = false;
+ }
+
+- int floorx = Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F);
+- if (Math.abs(floorx - this.yHeadRotp) >= 1) {
+- this.broadcast.accept(new ClientboundRotateHeadPacket(this.entity, (byte)floorx));
+- this.yHeadRotp = floorx;
++ i = Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F);
++ if (Math.abs(i - this.yHeadRotp) >= 1) {
++ this.broadcast.accept(new ClientboundRotateHeadPacket(this.entity, (byte) i));
++ this.yHeadRotp = i;
+ }
+
+ this.entity.hasImpulse = false;
+ }
+
+- this.tickCount++;
++ ++this.tickCount;
+ if (this.entity.hurtMarked) {
+- this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
++ // CraftBukkit start - Create PlayerVelocity event
++ boolean cancelled = false;
++
++ if (this.entity instanceof ServerPlayer) {
++ Player player = (Player) this.entity.getBukkitEntity();
++ org.bukkit.util.Vector velocity = player.getVelocity();
++
++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
++ this.entity.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ cancelled = true;
++ } else if (!velocity.equals(event.getVelocity())) {
++ player.setVelocity(event.getVelocity());
++ }
++ }
++
++ if (!cancelled) {
++ this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
++ }
++ // CraftBukkit end
+ this.entity.hurtMarked = false;
+ }
++
+ }
+
+ private static Stream<Entity> removedPassengers(List<Entity> initialPassengers, List<Entity> currentPassengers) {
+- return currentPassengers.stream().filter(entity -> !initialPassengers.contains(entity));
++ return currentPassengers.stream().filter((entity) -> {
++ return !initialPassengers.contains(entity);
++ });
+ }
+
+ public void removePairing(ServerPlayer player) {
+ this.entity.stopSeenByPlayer(player);
+- player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()));
++ player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()}));
+ }
+
+ public void addPairing(ServerPlayer player) {
+- List<Packet<ClientGamePacketListener>> list = new ArrayList<>();
++ List<Packet<ClientGamePacketListener>> list = new ArrayList();
++
++ Objects.requireNonNull(list);
+ this.sendPairingData(player, list::add);
+ player.connection.send(new ClientboundBundlePacket(list));
+ this.entity.startSeenByPlayer(player);
+@@ -229,24 +286,36 @@
+
+ public void sendPairingData(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> consumer) {
+ if (this.entity.isRemoved()) {
+- LOGGER.warn("Fetching packet for removed entity {}", this.entity);
++ // CraftBukkit start - Remove useless error spam, just return
++ // EntityTrackerEntry.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
++ return;
++ // CraftBukkit end
+ }
+
+- Packet<ClientGamePacketListener> addEntityPacket = this.entity.getAddEntityPacket();
++ Packet<ClientGamePacketListener> packet = this.entity.getAddEntityPacket();
++
+ this.yHeadRotp = Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F);
+- consumer.accept(addEntityPacket);
++ consumer.accept(packet);
+ if (this.trackedDataValues != null) {
+ consumer.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues));
+ }
+
+ boolean flag = this.trackDelta;
++
+ if (this.entity instanceof LivingEntity) {
+- Collection<AttributeInstance> syncableAttributes = ((LivingEntity)this.entity).getAttributes().getSyncableAttributes();
+- if (!syncableAttributes.isEmpty()) {
+- consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), syncableAttributes));
++ Collection<AttributeInstance> collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes();
++
++ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
++ if (this.entity.getId() == player.getId()) {
++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(collection, false);
+ }
++ // CraftBukkit end
+
+- if (((LivingEntity)this.entity).isFallFlying()) {
++ if (!collection.isEmpty()) {
++ consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection));
++ }
++
++ if (((LivingEntity) this.entity).isFallFlying()) {
+ flag = true;
+ }
+ }
+@@ -258,19 +327,30 @@
+
+ if (this.entity instanceof LivingEntity) {
+ List<Pair<EquipmentSlot, ItemStack>> list = Lists.newArrayList();
++ EquipmentSlot[] aenumitemslot = EquipmentSlot.values();
++ int i = aenumitemslot.length;
+
+- for (EquipmentSlot equipmentSlot : EquipmentSlot.values()) {
+- ItemStack itemBySlot = ((LivingEntity)this.entity).getItemBySlot(equipmentSlot);
+- if (!itemBySlot.isEmpty()) {
+- list.add(Pair.of(equipmentSlot, itemBySlot.copy()));
++ for (int j = 0; j < i; ++j) {
++ EquipmentSlot enumitemslot = aenumitemslot[j];
++ ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot);
++
++ if (!itemstack.isEmpty()) {
++ list.add(Pair.of(enumitemslot, itemstack.copy()));
+ }
+ }
+
+ if (!list.isEmpty()) {
+ consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
+ }
++ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
+ }
+
++ // CraftBukkit start - MC-109346: Fix for nonsensical head yaw
++ if (this.entity instanceof ServerPlayer) {
++ consumer.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F)));
++ }
++ // CraftBukkit end
++
+ if (!this.entity.getPassengers().isEmpty()) {
+ consumer.accept(new ClientboundSetPassengersPacket(this.entity));
+ }
+@@ -279,33 +359,49 @@
+ consumer.accept(new ClientboundSetPassengersPacket(this.entity.getVehicle()));
+ }
+
+- if (this.entity instanceof Mob mob && mob.isLeashed()) {
+- consumer.accept(new ClientboundSetEntityLinkPacket(mob, mob.getLeashHolder()));
++ Entity entity = this.entity;
++
++ if (entity instanceof Mob) {
++ Mob entityinsentient = (Mob) entity;
++
++ if (entityinsentient.isLeashed()) {
++ consumer.accept(new ClientboundSetEntityLinkPacket(entityinsentient, entityinsentient.getLeashHolder()));
++ }
+ }
++
+ }
+
+ private void sendDirtyEntityData() {
+- SynchedEntityData entityData = this.entity.getEntityData();
+- List<SynchedEntityData.DataValue<?>> list = entityData.packDirty();
++ SynchedEntityData datawatcher = this.entity.getEntityData();
++ List<SynchedEntityData.DataValue<?>> list = datawatcher.packDirty();
++
+ if (list != null) {
+- this.trackedDataValues = entityData.getNonDefaultValues();
++ this.trackedDataValues = datawatcher.getNonDefaultValues();
+ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
+ }
+
+ if (this.entity instanceof LivingEntity) {
+- Set<AttributeInstance> dirtyAttributes = ((LivingEntity)this.entity).getAttributes().getDirtyAttributes();
+- if (!dirtyAttributes.isEmpty()) {
+- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), dirtyAttributes));
++ Set<AttributeInstance> set = ((LivingEntity) this.entity).getAttributes().getDirtyAttributes();
++
++ if (!set.isEmpty()) {
++ // CraftBukkit start - Send scaled max health
++ if (this.entity instanceof ServerPlayer) {
++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
++ }
++ // CraftBukkit end
++ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
+ }
+
+- dirtyAttributes.clear();
++ set.clear();
+ }
++
+ }
+
+ private void broadcastAndSend(Packet<?> packet) {
+ this.broadcast.accept(packet);
+ if (this.entity instanceof ServerPlayer) {
+- ((ServerPlayer)this.entity).connection.send(packet);
++ ((ServerPlayer) this.entity).connection.send(packet);
+ }
++
+ }
+ }