diff --git a/pom.xml b/pom.xml
index ff01e3e..9c0ffd2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
me.xginko
VillagerOptimizer
- 1.5.1
+ 1.5.2
jar
VillagerOptimizer
@@ -23,7 +23,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.11.0
+ 3.12.1
${java.version}
${java.version}
diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java
index 172613a..0b4279d 100644
--- a/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java
+++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java
@@ -6,6 +6,7 @@ import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.utils.GenericUtil;
+import me.xginko.villageroptimizer.utils.LocationUtil;
import org.bukkit.Chunk;
import org.bukkit.Server;
import org.bukkit.World;
@@ -22,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
@@ -49,9 +51,19 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
this.log_enabled = config.getBoolean(configPath() + ".log-removals", true);
this.non_optimized_max_per_chunk = config.getInt(configPath() + ".unoptimized.max-per-chunk", 20,
"The maximum amount of unoptimized villagers per chunk.");
- this.non_optimized_removal_priority = config.getList(configPath() + ".unoptimized.removal-priority", Arrays.asList(
- "NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
- "FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"),
+ final List defaults = Stream.of(
+ "NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
+ "FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN")
+ .filter(profession -> {
+ try {
+ // Make sure no scary warnings appear when creating config defaults
+ Villager.Profession.valueOf(profession);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }).collect(Collectors.toList());
+ this.non_optimized_removal_priority = config.getList(configPath() + ".unoptimized.removal-priority", defaults,
"Professions that are in the top of the list are going to be scheduled for removal first.\n" +
"Use enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html")
.stream()
@@ -69,9 +81,7 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
.collect(Collectors.toList());
this.optimized_max_per_chunk = config.getInt(configPath() + ".optimized.max-per-chunk", 60,
"The maximum amount of optimized villagers per chunk.");
- this.optimized_removal_priority = config.getList(configPath() + ".optimized.removal-priority", Arrays.asList(
- "NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
- "FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"))
+ this.optimized_removal_priority = config.getList(configPath() + ".optimized.removal-priority", defaults)
.stream()
.map(configuredProfession -> {
try {
@@ -164,8 +174,8 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
scheduler.runAtEntity(villager, kill -> {
villager.remove();
if (log_enabled) {
- info("Removed unoptimized villager with profession '" + villager.getProfession() + "' at " +
- GenericUtil.formatLocation(villager.getLocation()));
+ info("Removed unoptimized villager with profession '" + GenericUtil.formatEnum(villager.getProfession()) + "' at " +
+ LocationUtil.toString(villager.getLocation()));
}
});
}
@@ -186,8 +196,8 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
villager.remove();
if (log_enabled) {
- info("Removed optimized villager with profession '" + villager.getProfession() + "' at " +
- GenericUtil.formatLocation(villager.getLocation()));
+ info("Removed optimized villager with profession '" + GenericUtil.formatEnum(villager.getProfession()) + "' at " +
+ LocationUtil.toString(villager.getLocation()));
}
});
}
diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java
index 45f8ba4..1c066d8 100644
--- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java
+++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java
@@ -5,7 +5,7 @@ import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
-import me.xginko.villageroptimizer.utils.GenericUtil;
+import me.xginko.villageroptimizer.utils.LocationUtil;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
@@ -90,7 +90,7 @@ public class EnableLeashingVillagers implements VillagerOptimizerModule, Listene
handItem.subtract(1); // Manually consume for survival players
if (log_enabled) {
- info(player.getName() + " leashed a villager at " + GenericUtil.formatLocation(villager.getLocation()));
+ info(player.getName() + " leashed a villager at " + LocationUtil.toString(villager.getLocation()));
}
});
}
diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java
index 50672f3..75b5544 100644
--- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java
+++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java
@@ -2,6 +2,7 @@ package me.xginko.villageroptimizer.modules.gameplay;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
+import me.xginko.villageroptimizer.utils.LocationUtil;
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.Permissions;
@@ -85,7 +86,7 @@ public class RestockOptimizedTrades implements VillagerOptimizerModule, Listener
}
if (log_enabled) {
- info("Restocked optimized villager at " + GenericUtil.formatLocation(wVillager.villager().getLocation()));
+ info("Restocked optimized villager at " + LocationUtil.toString(wVillager.villager().getLocation()));
}
}
}
diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java
index 6bd6ac2..f81d54c 100644
--- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java
+++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java
@@ -2,7 +2,6 @@ package me.xginko.villageroptimizer.modules.optimization;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
-import me.xginko.villageroptimizer.wrapper.WrappedVillager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions;
@@ -11,7 +10,8 @@ import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.GenericUtil;
import me.xginko.villageroptimizer.utils.KyoriUtil;
-import net.kyori.adventure.text.Component;
+import me.xginko.villageroptimizer.utils.LocationUtil;
+import me.xginko.villageroptimizer.wrapper.WrappedVillager;
import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -26,7 +26,10 @@ import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import java.time.Duration;
-import java.util.*;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -110,7 +113,8 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
for (Villager villager : blockLoc.getNearbyEntitiesByType(Villager.class, search_radius)) {
final Villager.Profession profession = villager.getProfession();
if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue;
- final double distance = villager.getLocation().distanceSquared(blockLoc);
+
+ final double distance = LocationUtil.relDistanceSquared3D(villager.getLocation(), blockLoc);
if (distance >= closestDistance) continue;
final WrappedVillager wVillager = villagerCache.getOrAdd(villager);
@@ -137,19 +141,19 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
- .replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(closestOptimizableVillager.villager().getProfession()))
.build();
final TextReplacementConfig placedMaterial = TextReplacementConfig.builder()
.matchLiteral("%blocktype%")
- .replacement(placed.getType().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(placed.getType()))
.build();
VillagerOptimizer.getLang(player.locale()).block_optimize_success
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(placedMaterial)));
}
if (log_enabled) {
- VillagerOptimizer.getPrefixedLogger().info(Component.text(player.getName() + " optimized villager by block at " +
- GenericUtil.formatLocation(closestOptimizableVillager.villager().getLocation())).color(GenericUtil.COLOR));
+ info(player.getName() + " optimized villager at " +
+ LocationUtil.toString(closestOptimizableVillager.villager().getLocation()));
}
} else {
closestOptimizableVillager.sayNo();
@@ -177,7 +181,7 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
double closestDistance = Double.MAX_VALUE;
for (Villager villager : blockLoc.getNearbyEntitiesByType(Villager.class, search_radius)) {
- final double distance = villager.getLocation().distanceSquared(blockLoc);
+ final double distance = LocationUtil.relDistanceSquared3D(villager.getLocation(), blockLoc);
if (distance >= closestDistance) continue;
final WrappedVillager wVillager = villagerCache.getOrAdd(villager);
@@ -202,19 +206,19 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
- .replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(closestOptimizedVillager.villager().getProfession()))
.build();
final TextReplacementConfig brokenMaterial = TextReplacementConfig.builder()
.matchLiteral("%blocktype%")
- .replacement(broken.getType().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(broken.getType()))
.build();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(brokenMaterial)));
}
if (log_enabled) {
- VillagerOptimizer.getPrefixedLogger().info(Component.text(player.getName() + " unoptimized villager by block at " +
- GenericUtil.formatLocation(closestOptimizedVillager.villager().getLocation())).color(GenericUtil.COLOR));
+ info(player.getName() + " unoptimized villager using " + GenericUtil.formatEnum(broken.getType()) +
+ LocationUtil.toString(closestOptimizedVillager.villager().getLocation()));
}
}
}
\ No newline at end of file
diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java
index 9612f2f..43b1ea0 100644
--- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java
+++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java
@@ -2,6 +2,7 @@ package me.xginko.villageroptimizer.modules.optimization;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
+import me.xginko.villageroptimizer.utils.LocationUtil;
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
@@ -123,7 +124,7 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
if (log_enabled) {
info(player.getName() + " optimized villager using nametag '" + nameTagPlainText + "' at " +
- GenericUtil.formatLocation(wVillager.villager().getLocation()));
+ LocationUtil.toString(wVillager.villager().getLocation()));
}
} else {
event.setCancelled(true);
@@ -156,7 +157,7 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
if (log_enabled) {
info(player.getName() + " unoptimized villager using nametag '" + nameTagPlainText + "' at " +
- GenericUtil.formatLocation(wVillager.villager().getLocation()));
+ LocationUtil.toString(wVillager.villager().getLocation()));
}
}
}
diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java
index e366ab2..770ec2c 100644
--- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java
+++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java
@@ -3,6 +3,7 @@ package me.xginko.villageroptimizer.modules.optimization;
import com.tcoded.folialib.impl.ServerImplementation;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
+import me.xginko.villageroptimizer.utils.LocationUtil;
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
@@ -108,7 +109,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
if (villager.getProfession() != workstationProfession) continue;
WrappedVillager wrapped = villagerCache.getOrAdd(villager);
if (wrapped.getJobSite() == null) continue;
- if (wrapped.getJobSite().distanceSquared(workstationLoc) > 1) continue;
+ if (LocationUtil.relDistanceSquared3D(wrapped.getJobSite(), workstationLoc) > 1) continue;
if (!wrapped.canOptimize(cooldown_millis) && !player.hasPermission(Permissions.Bypass.WORKSTATION_COOLDOWN.get())) {
wrapped.sayNo();
@@ -139,19 +140,19 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
- .replacement(wrapped.villager().getProfession().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(wrapped.villager().getProfession()))
.build();
final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder()
.matchLiteral("%blocktype%")
- .replacement(placed.getType().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(placed.getType()))
.build();
VillagerOptimizer.getLang(player.locale()).workstation_optimize_success
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(placedWorkstation)));
}
if (log_enabled) {
- info(player.getName() + " optimized villager using workstation " + placed.getType() + " at " +
- GenericUtil.formatLocation(wrapped.villager().getLocation()));
+ info(player.getName() + " optimized villager using workstation " + GenericUtil.formatEnum(placed.getType()) + " at " +
+ LocationUtil.toString(wrapped.villager().getLocation()));
}
taskComplete.set(true);
@@ -176,7 +177,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
for (Villager villager : workstationLoc.getNearbyEntitiesByType(Villager.class, search_radius)) {
if (!villager.getProfession().equals(workstationProfession)) continue;
- final double distance = villager.getLocation().distanceSquared(workstationLoc);
+ final double distance = LocationUtil.relDistanceSquared3D(villager.getLocation(), workstationLoc);
if (distance >= closestDistance) continue;
WrappedVillager wrapped = villagerCache.getOrAdd(villager);
@@ -203,19 +204,19 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
- .replacement(closestOptimized.villager().getProfession().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(closestOptimized.villager().getProfession()))
.build();
final TextReplacementConfig brokenWorkstation = TextReplacementConfig.builder()
.matchLiteral("%blocktype%")
- .replacement(broken.getType().toString().toLowerCase())
+ .replacement(GenericUtil.formatEnum(broken.getType()))
.build();
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(brokenWorkstation)));
}
if (log_enabled) {
- info(player.getName() + " unoptimized villager using workstation " + broken.getType() + " at " +
- GenericUtil.formatLocation(closestOptimized.villager().getLocation()));
+ info(player.getName() + " unoptimized villager using workstation " + GenericUtil.formatEnum(broken.getType()) + " at " +
+ LocationUtil.toString(closestOptimized.villager().getLocation()));
}
}
}
\ No newline at end of file
diff --git a/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java b/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java
index 4782fc1..bd4ed49 100644
--- a/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java
+++ b/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java
@@ -3,15 +3,14 @@ package me.xginko.villageroptimizer.utils;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
-import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Chunk;
-import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Villager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
+import java.util.Locale;
public class GenericUtil {
@@ -34,8 +33,16 @@ public class GenericUtil {
}
}
- public static @NotNull String formatLocation(@NotNull Location location) {
- return "[" + location.getWorld().getName() + "] x=" + location.getBlockX() + ", y=" + location.getBlockY() + ", z=" + location.getBlockZ();
+ public static @NotNull String formatEnum(@NotNull Enum> input) {
+ // Turn something like "REDSTONE_TORCH" into "redstone torch"
+ String[] lowercaseWords = input.name().toLowerCase(Locale.ROOT).split("-");
+ for (int i = 0; i < lowercaseWords.length; i++) {
+ String word = lowercaseWords[i];
+ // Capitalize first letter for each word
+ lowercaseWords[i] = word.substring(0, 1).toUpperCase() + word.substring(1);
+ }
+ // return as nice string
+ return String.join(" ", lowercaseWords);
}
private static boolean specificChunkLoadedMethodAvailable = true;
diff --git a/src/main/java/me/xginko/villageroptimizer/utils/LocationUtil.java b/src/main/java/me/xginko/villageroptimizer/utils/LocationUtil.java
new file mode 100644
index 0000000..9b26371
--- /dev/null
+++ b/src/main/java/me/xginko/villageroptimizer/utils/LocationUtil.java
@@ -0,0 +1,61 @@
+package me.xginko.villageroptimizer.utils;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.util.NumberConversions;
+import org.jetbrains.annotations.NotNull;
+
+public class LocationUtil {
+
+ public static @NotNull String toString(@NotNull Location location) {
+ return "[" + location.getWorld().getName() + "] x=" + location.getBlockX() + ", y=" + location.getBlockY() + ", z=" + location.getBlockZ();
+ }
+
+ public static double relDistanceSquared2D(@NotNull Location from, @NotNull Location to) {
+ World.Environment toEnv = to.getWorld().getEnvironment();
+ World.Environment fromEnv = from.getWorld().getEnvironment();
+ double toX = to.getX();
+ double toZ = to.getZ();
+ double fromX = from.getX();
+ double fromZ = from.getZ();
+
+ // Make sure distance is relative since one block in the nether equates to 8 in the overworld/end
+ if (toEnv != fromEnv) {
+ if (fromEnv == World.Environment.NETHER) {
+ fromX *= 8;
+ fromZ *= 8;
+ }
+ if (toEnv == World.Environment.NETHER) {
+ toX *= 8;
+ toZ *= 8;
+ }
+ }
+
+ return NumberConversions.square(toX - fromX) + NumberConversions.square(toZ - fromZ);
+ }
+
+ public static double relDistanceSquared3D(@NotNull Location from, @NotNull Location to) {
+ double toY = to.getY();
+ double fromY = from.getY();
+
+ // Clamp Y levels the same way minecraft would for portal creation logic
+ if (fromY < to.getWorld().getMinHeight())
+ fromY = to.getWorld().getMinHeight();
+ if (fromY > to.getWorld().getMaxHeight())
+ fromY = to.getWorld().getMaxHeight();
+ if (toY < from.getWorld().getMinHeight())
+ toY = from.getWorld().getMinHeight();
+ if (toY > from.getWorld().getMaxHeight())
+ toY = from.getWorld().getMaxHeight();
+
+ return relDistanceSquared2D(from, to) + NumberConversions.square(toY - fromY);
+ }
+
+ public static double relDistance2D(@NotNull Location from, @NotNull Location to) {
+ return Math.sqrt(relDistanceSquared2D(from, to));
+ }
+
+ public static double relDistance3D(@NotNull Location from, @NotNull Location to) {
+ return Math.sqrt(relDistanceSquared3D(from, to));
+ }
+}