finish optimization interactions

This commit is contained in:
xGinko 2023-09-08 13:55:57 +02:00
parent e29bb8fcb1
commit 61ffc8e198
10 changed files with 187 additions and 119 deletions

View File

@ -1,22 +1,13 @@
package me.xginko.villageroptimizer; package me.xginko.villageroptimizer;
import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.config.LanguageCache; import me.xginko.villageroptimizer.config.LanguageCache;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.models.WrappedVillager;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Location;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.block.BlockFace;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -146,54 +137,4 @@ public final class VillagerOptimizer extends JavaPlugin {
return getLang(config.default_lang); return getLang(config.default_lang);
} }
} }
public static OptimizationType computeOptimization(@NotNull WrappedVillager wrapped) {
if (config.enable_nametag_optimization) {
Component name = wrapped.villager().customName();
if (name != null && config.nametags.contains(PlainTextComponentSerializer.plainText().serialize(name).toLowerCase())) {
return OptimizationType.NAMETAG;
}
}
if (config.enable_block_optimization) {
if (config.blocks_that_disable.contains(wrapped.villager().getLocation().getBlock().getRelative(BlockFace.DOWN).getType())) {
return OptimizationType.BLOCK;
}
}
if (config.enable_workstation_optimization) {
final Location jobSite = wrapped.villager().getMemory(MemoryKey.JOB_SITE);
if (
jobSite != null
&& config.workstations_that_disable.contains(jobSite.getBlock().getType())
&& wrapped.villager().getLocation().distance(jobSite) <= config.workstation_max_distance
) {
return OptimizationType.WORKSTATION;
}
}
return wrapped.getOptimizationType();
}
public static OptimizationType computeOptimization(@NotNull Villager villager) {
if (config.enable_nametag_optimization) {
Component name = villager.customName();
if (name != null && config.nametags.contains(PlainTextComponentSerializer.plainText().serialize(name).toLowerCase())) {
return OptimizationType.NAMETAG;
}
}
if (config.enable_block_optimization) {
if (config.blocks_that_disable.contains(villager.getLocation().getBlock().getRelative(BlockFace.DOWN).getType())) {
return OptimizationType.BLOCK;
}
}
if (config.enable_workstation_optimization) {
final Location jobSite = villager.getMemory(MemoryKey.JOB_SITE);
if (
jobSite != null
&& config.workstations_that_disable.contains(jobSite.getBlock().getType())
&& villager.getLocation().distance(jobSite) <= config.workstation_max_distance
) {
return OptimizationType.WORKSTATION;
}
}
return villagerManager.getOrAdd(villager).getOptimizationType();
}
} }

View File

@ -16,7 +16,7 @@ public class LanguageCache {
public final Component no_permission; public final Component no_permission;
public final List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success, public final List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success,
block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success, block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success,
workstation_optimization_success, workstation_on_optimize_cooldown, workstation_unoptimize_success; workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success;
public LanguageCache(String lang) throws Exception { public LanguageCache(String lang) throws Exception {
this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml")); this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml"));
@ -28,10 +28,10 @@ public class LanguageCache {
this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success", List.of("<green>Successfully unoptimized villager by using a nametag.")); this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success", List.of("<green>Successfully unoptimized villager by using a nametag."));
this.block_optimize_success = getListTranslation("messages.block.optimize-success", List.of("<green>%villagertype% villager successfully optimized using block %blocktype%.")); this.block_optimize_success = getListTranslation("messages.block.optimize-success", List.of("<green>%villagertype% villager successfully optimized using block %blocktype%."));
this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown", List.of("<gray>You need to wait %time% until you can optimize this villager again.")); this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown", List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success", List.of("<green>Successfully unoptimized villager by moving it off a %blocktype% block.")); this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success", List.of("<green>Successfully unoptimized %villagertype% villager by removing %blocktype%."));
this.workstation_optimization_success = getListTranslation("messages.workstation.optimize-success", List.of("<green>%villagertype% villager successfully optimized using workstation block %blocktype%.")); this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success", List.of("<green>%villagertype% villager successfully optimized using workstation %workstation%."));
this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown", List.of("<gray>You need to wait %time% until you can optimize this villager again.")); this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown", List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success", List.of("<green>Successfully unoptimized villager by removing workstation block %blocktype%.")); this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success", List.of("<green>Successfully unoptimized %villagertype% villager by removing workstation block %workstation%."));
saveLang(); saveLang();
} }

View File

@ -11,11 +11,11 @@ import org.jetbrains.annotations.NotNull;
public final class WrappedVillager { public final class WrappedVillager {
private final @NotNull Villager villager; private final @NotNull Villager villager;
private final @NotNull PersistentDataContainer villagerData; private final @NotNull PersistentDataContainer dataContainer;
public WrappedVillager(@NotNull Villager villager) { public WrappedVillager(@NotNull Villager villager) {
this.villager = villager; this.villager = villager;
this.villagerData = this.villager.getPersistentDataContainer(); this.dataContainer = this.villager.getPersistentDataContainer();
} }
public @NotNull Villager villager() { public @NotNull Villager villager() {
@ -27,41 +27,37 @@ public final class WrappedVillager {
} }
public boolean isOptimized() { public boolean isOptimized() {
return villagerData.has(Keys.OPTIMIZED.key()); return dataContainer.has(Keys.OPTIMIZED.key());
}
public @NotNull OptimizationType computeOptimization() {
return VillagerOptimizer.computeOptimization(this);
} }
public boolean setOptimization(OptimizationType type) { public boolean setOptimization(OptimizationType type) {
if (type.equals(OptimizationType.OFF) && isOptimized()) { if (type.equals(OptimizationType.OFF) && isOptimized()) {
villagerData.remove(Keys.OPTIMIZED.key()); dataContainer.remove(Keys.OPTIMIZED.key());
villager.setAware(true); villager.setAware(true);
villager.setAI(true); villager.setAI(true);
} else { } else {
if (isOnOptimizeCooldown()) return false; if (isOnOptimizeCooldown()) return false;
setOptimizeCooldown(VillagerOptimizer.getConfiguration().state_change_cooldown); dataContainer.set(Keys.OPTIMIZED.key(), PersistentDataType.STRING, type.name());
villagerData.set(Keys.OPTIMIZED.key(), PersistentDataType.STRING, type.name());
villager.setAware(false); villager.setAware(false);
setOptimizeCooldown(VillagerOptimizer.getConfiguration().state_change_cooldown);
} }
return true; return true;
} }
public @NotNull OptimizationType getOptimizationType() { public @NotNull OptimizationType getOptimizationType() {
return isOptimized() ? OptimizationType.valueOf(villagerData.get(Keys.OPTIMIZED.key(), PersistentDataType.STRING)) : OptimizationType.OFF; return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZED.key(), PersistentDataType.STRING)) : OptimizationType.OFF;
} }
public void setOptimizeCooldown(long milliseconds) { public void setOptimizeCooldown(long milliseconds) {
villagerData.set(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); dataContainer.set(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds);
} }
public long getOptimizeCooldown() { public long getOptimizeCooldown() {
return villagerData.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) ? System.currentTimeMillis() - villagerData.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) : 0L; return dataContainer.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) ? System.currentTimeMillis() - dataContainer.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) : 0L;
} }
public boolean isOnOptimizeCooldown() { public boolean isOnOptimizeCooldown() {
return villagerData.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) && villagerData.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); return dataContainer.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) && dataContainer.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) <= System.currentTimeMillis();
} }
public void restock() { public void restock() {
@ -69,20 +65,20 @@ public final class WrappedVillager {
} }
public void setExpCooldown(long milliseconds) { public void setExpCooldown(long milliseconds) {
villagerData.set(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); dataContainer.set(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds);
} }
public boolean isOnExpCooldown() { public boolean isOnExpCooldown() {
return villagerData.has(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) && villagerData.get(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); return dataContainer.has(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) && dataContainer.get(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) <= System.currentTimeMillis();
} }
public long saveWorldTime() { public long saveWorldTime() {
final long worldTime = villager.getWorld().getFullTime(); final long worldTime = villager.getWorld().getFullTime();
villagerData.set(Keys.WORLDTIME.key(), PersistentDataType.LONG, worldTime); dataContainer.set(Keys.WORLDTIME.key(), PersistentDataType.LONG, worldTime);
return worldTime; return worldTime;
} }
public long getSavedWorldTime() { public long getSavedWorldTime() {
return villagerData.has(Keys.WORLDTIME.key(), PersistentDataType.LONG) ? villagerData.get(Keys.WORLDTIME.key(), PersistentDataType.LONG) : saveWorldTime(); return dataContainer.has(Keys.WORLDTIME.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.WORLDTIME.key(), PersistentDataType.LONG) : saveWorldTime();
} }
} }

View File

@ -61,14 +61,17 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
Block placed = event.getBlock(); Block placed = event.getBlock();
if (!config.blocks_that_disable.contains(placed.getType())) return; if (!config.blocks_that_disable.contains(placed.getType())) return;
placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,0.5,0.5).forEach(entity -> { placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> {
if (entity.getType().equals(EntityType.VILLAGER)) { if (entity.getType().equals(EntityType.VILLAGER)) {
WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity);
if (!wVillager.isOptimized()) { if (!wVillager.isOptimized()) {
if (wVillager.setOptimization(OptimizationType.BLOCK)) { if (wVillager.setOptimization(OptimizationType.BLOCK)) {
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(player::sendMessage); VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placed.getType().toString().toLowerCase()).build())
));
} }
if (shouldLog) if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
@ -89,14 +92,17 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
Block broken = event.getBlock(); Block broken = event.getBlock();
if (!config.blocks_that_disable.contains(broken.getType())) return; if (!config.blocks_that_disable.contains(broken.getType())) return;
broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,0.5,0.5).forEach(entity -> { broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> {
if (entity.getType().equals(EntityType.VILLAGER)) { if (entity.getType().equals(EntityType.VILLAGER)) {
WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity);
if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) {
wVillager.setOptimization(OptimizationType.OFF); wVillager.setOptimization(OptimizationType.OFF);
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(player::sendMessage); VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(broken.getType().toString().toLowerCase()).build())
));
} }
if (shouldLog) if (shouldLog)
VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation());
@ -121,7 +127,10 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
if (wVillager.setOptimization(OptimizationType.BLOCK)) { if (wVillager.setOptimization(OptimizationType.BLOCK)) {
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(player::sendMessage); VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(entityLegs.getBlock().getType().toString().toLowerCase()).build())
));
} }
if (shouldLog) if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
@ -138,7 +147,10 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
wVillager.setOptimization(OptimizationType.OFF); wVillager.setOptimization(OptimizationType.OFF);
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(player::sendMessage); VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(entityLegs.getBlock().getType().toString().toLowerCase()).build())
));
} }
if (shouldLog) if (shouldLog)
VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation());

View File

@ -37,16 +37,20 @@ public class PreventVillagerDamage implements VillagerOptimizerModule, Listener
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onDamageReceive(EntityDamageEvent event) { private void onDamageReceive(EntityDamageEvent event) {
if (!event.getEntityType().equals(EntityType.VILLAGER)) return; if (
if (villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized()) { !event.getEntityType().equals(EntityType.VILLAGER)
&& villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized()
) {
event.setCancelled(true); event.setCancelled(true);
} }
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onPushByEntityAttack(EntityPushedByEntityAttackEvent event) { private void onPushByEntityAttack(EntityPushedByEntityAttackEvent event) {
if (!event.getEntityType().equals(EntityType.VILLAGER)) return; if (
if (villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized()) { !event.getEntityType().equals(EntityType.VILLAGER)
&& villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized()
) {
event.setCancelled(true); event.setCancelled(true);
} }
} }

View File

@ -3,6 +3,7 @@ package me.xginko.villageroptimizer.modules;
import com.destroystokyo.paper.event.entity.EntityPathfindEvent; import com.destroystokyo.paper.event.entity.EntityPathfindEvent;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.cache.VillagerManager; import me.xginko.villageroptimizer.cache.VillagerManager;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Mob; import org.bukkit.entity.Mob;
import org.bukkit.entity.Villager; import org.bukkit.entity.Villager;
@ -39,14 +40,25 @@ public class PreventVillagerTargetting implements VillagerOptimizerModule, Liste
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onTarget(EntityTargetLivingEntityEvent event) { private void onTarget(EntityTargetLivingEntityEvent event) {
if (event.getTarget() instanceof Villager villager && villagerManager.getOrAdd(villager).isOptimized()) { // Yes, instanceof checks would look way more beautiful here but checking type is much faster
Entity target = event.getTarget();
if (
target != null
&& target.getType().equals(EntityType.VILLAGER)
&& villagerManager.getOrAdd((Villager) target).isOptimized()
) {
event.setCancelled(true); event.setCancelled(true);
} }
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onEntityTargetVillager(EntityPathfindEvent event) { private void onEntityTargetVillager(EntityPathfindEvent event) {
if (event.getTargetEntity() instanceof Villager villager && villagerManager.getOrAdd(villager).isOptimized()) { Entity target = event.getTargetEntity();
if (
target != null
&& target.getType().equals(EntityType.VILLAGER)
&& villagerManager.getOrAdd((Villager) target).isOptimized()
) {
event.setCancelled(true); event.setCancelled(true);
} }
} }

View File

@ -2,11 +2,10 @@ package me.xginko.villageroptimizer.modules;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.models.WrappedVillager;
import me.xginko.villageroptimizer.utils.LogUtils; import me.xginko.villageroptimizer.utils.LogUtils;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Villager; import org.bukkit.entity.Villager;
@ -15,24 +14,27 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
public class ChunkLimit implements VillagerOptimizerModule, Listener { public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
private final VillagerOptimizer plugin; private final VillagerOptimizer plugin;
private final VillagerManager villagerManager;
private ScheduledTask scheduledTask; private ScheduledTask scheduledTask;
private final List<Villager.Profession> removalPriority = new ArrayList<>(); private final List<Villager.Profession> removalPriority = new ArrayList<>(16);
private final int maxVillagersPerChunk; private final int maxVillagersPerChunk;
private final boolean logIsEnabled; private final boolean logIsEnabled;
private final long checkPeriod; private final long checkPeriod;
protected ChunkLimit() { protected VillagerChunkLimit() {
shouldEnable(); shouldEnable();
this.plugin = VillagerOptimizer.getInstance(); this.plugin = VillagerOptimizer.getInstance();
this.villagerManager = VillagerOptimizer.getVillagerManager();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
this.maxVillagersPerChunk = config.getInt("villager-chunk-limit.max-villagers-per-chunk", 25); this.maxVillagersPerChunk = config.getInt("villager-chunk-limit.max-villagers-per-chunk", 25);
this.logIsEnabled = config.getBoolean("villager-chunk-limit.log-removals", false); this.logIsEnabled = config.getBoolean("villager-chunk-limit.log-removals", false);
@ -70,19 +72,28 @@ public class ChunkLimit implements VillagerOptimizerModule, Listener {
if (scheduledTask != null) scheduledTask.cancel(); if (scheduledTask != null) scheduledTask.cancel();
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onCreatureSpawn(CreatureSpawnEvent event) {
Entity spawned = event.getEntity();
if (spawned.getType().equals(EntityType.VILLAGER)) {
checkVillagersInChunk(spawned.getChunk());
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onCreateSpawn(CreatureSpawnEvent event) { private void onInteract(PlayerInteractEntityEvent event) {
if (event.getEntityType().equals(EntityType.VILLAGER)) { Entity clicked = event.getRightClicked();
checkVillagersInChunk(event.getEntity().getChunk()); if (clicked.getType().equals(EntityType.VILLAGER)) {
checkVillagersInChunk(clicked.getChunk());
} }
} }
private void run() { private void run() {
for (World world : plugin.getServer().getWorlds()) { plugin.getServer().getWorlds().forEach(world -> {
for (Chunk chunk : world.getLoadedChunks()) { for (Chunk chunk : world.getLoadedChunks()) {
plugin.getServer().getRegionScheduler().run(plugin, world, chunk.getX(), chunk.getZ(), task -> checkVillagersInChunk(chunk)); plugin.getServer().getRegionScheduler().run(plugin, world, chunk.getX(), chunk.getZ(), task -> checkVillagersInChunk(chunk));
} }
} });
} }
private void checkVillagersInChunk(Chunk chunk) { private void checkVillagersInChunk(Chunk chunk) {
@ -104,17 +115,14 @@ public class ChunkLimit implements VillagerOptimizerModule, Listener {
// Remove prioritized villagers that are too many // Remove prioritized villagers that are too many
for (int i = 0; i < amount_over_the_limit; i++) { for (int i = 0; i < amount_over_the_limit; i++) {
Villager villager = villagers_in_chunk.get(i); Villager villager = villagers_in_chunk.get(i);
if (logIsEnabled) LogUtils.moduleLog(
Level.INFO,
"villager-chunk-limit",
"Removing villager of profession type '"+villager.getProfession()+"' at "+villager.getLocation()
);
villager.remove(); villager.remove();
if (logIsEnabled) LogUtils.moduleLog(Level.INFO, "villager-chunk-limit",
"Removed villager of profession type '"+villager.getProfession()+"' at "+villager.getLocation());
} }
} }
private int getProfessionPriority(Villager villager) { private int getProfessionPriority(Villager villager) {
Villager.Profession profession = villager.getProfession(); final Villager.Profession profession = villager.getProfession();
return removalPriority.contains(profession) && !WrappedVillager.fromCache(villager).isOptimized() ? removalPriority.indexOf(profession) : Integer.MAX_VALUE; return removalPriority.contains(profession) && !villagerManager.getOrAdd(villager).isOptimized() ? removalPriority.indexOf(profession) : Integer.MAX_VALUE;
} }
} }

View File

@ -14,7 +14,7 @@ public interface VillagerOptimizerModule {
modules.forEach(VillagerOptimizerModule::disable); modules.forEach(VillagerOptimizerModule::disable);
modules.clear(); modules.clear();
modules.add(new ChunkLimit()); modules.add(new VillagerChunkLimit());
modules.add(new NametagOptimization()); modules.add(new NametagOptimization());
modules.add(new BlockOptimization()); modules.add(new BlockOptimization());
modules.add(new WorkstationOptimization()); modules.add(new WorkstationOptimization());

View File

@ -1,28 +1,46 @@
package me.xginko.villageroptimizer.modules; package me.xginko.villageroptimizer.modules;
import io.papermc.paper.event.entity.EntityMoveEvent;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.cache.VillagerManager; import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.models.WrappedVillager;
import me.xginko.villageroptimizer.utils.CommonUtils;
import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import java.util.Comparator;
import java.util.List;
public class WorkstationOptimization implements VillagerOptimizerModule, Listener { public class WorkstationOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final Config config; private final Config config;
private final boolean shouldLog, shouldNotifyPlayer; private final boolean shouldLog, shouldNotifyPlayer;
private final double search_radius;
protected WorkstationOptimization() { protected WorkstationOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager(); this.villagerManager = VillagerOptimizer.getVillagerManager();
this.config = VillagerOptimizer.getConfiguration(); this.config = VillagerOptimizer.getConfiguration();
this.config.addComment("optimization.methods.by-workstation.enable", """ this.config.addComment("optimization.methods.by-workstation.enable", """
When enabled, villagers near a configured radius to a workstation specific to their profession\s When enabled, villagers near a configured radius to a workstation specific to your config\s
will be optimized. will be optimized.
"""); """);
this.search_radius = config.getDouble("optimization.methods.by-workstation.search-radius-in-blocks", 4.0, """
The radius in blocks a villager can be away from the player when he places a workstation.\s
The closest unoptimized villager to the player will be optimized.
""");
this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false); this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false);
this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-workstation.notify-player", true); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-workstation.notify-player", true);
} }
@ -43,10 +61,87 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
return config.enable_workstation_optimization; return config.enable_workstation_optimization;
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onEntityMove(EntityMoveEvent event) { private void onBlockPlace(BlockPlaceEvent event) {
if (!event.getEntity().getType().equals(EntityType.VILLAGER)) return; Block placed = event.getBlock();
if (!config.workstations_that_disable.contains(placed.getType())) return;
final Location workstationLoc = placed.getLocation();
List<Entity> nearbyUnoptimized = workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)
.stream().sorted(Comparator.comparingInt(entity -> {
if (entity.getType().equals(EntityType.VILLAGER)) {
Villager villager = (Villager) entity;
Villager.Profession profession = villager.getProfession();
if (
!profession.equals(Villager.Profession.NONE)
&& !profession.equals(Villager.Profession.NITWIT)
&& !villagerManager.getOrAdd(villager).isOptimized()
) {
return (int) entity.getLocation().distance(workstationLoc);
}
}
return Integer.MAX_VALUE;
})).toList();
if (nearbyUnoptimized.isEmpty()) return;
WrappedVillager closest = villagerManager.getOrAdd((Villager) nearbyUnoptimized.get(0));
if (closest.setOptimization(OptimizationType.WORKSTATION)) {
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).workstation_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(closest.villager().getProfession().toString().toLowerCase()).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%workstation%").replacement(placed.getType().toString().toLowerCase()).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info(event.getPlayer().getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
} else {
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(closest.getOptimizeCooldown())).build())));
}
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockBreak(BlockBreakEvent event) {
Block placed = event.getBlock();
if (!config.workstations_that_disable.contains(placed.getType())) return;
final Location workstationLoc = placed.getLocation();
List<Entity> nearbyOptimized = workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)
.stream().sorted(Comparator.comparingInt(entity -> {
if (entity.getType().equals(EntityType.VILLAGER)) {
Villager villager = (Villager) entity;
Villager.Profession profession = villager.getProfession();
if (
!profession.equals(Villager.Profession.NONE)
&& !profession.equals(Villager.Profession.NITWIT)
&& villagerManager.getOrAdd(villager).isOptimized()
) {
return (int) entity.getLocation().distance(workstationLoc);
}
}
return Integer.MAX_VALUE;
})).toList();
if (nearbyOptimized.isEmpty()) return;
WrappedVillager closest = villagerManager.getOrAdd((Villager) nearbyOptimized.get(0));
if (closest.getOptimizationType().equals(OptimizationType.WORKSTATION)) {
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(closest.villager().getProfession().toString().toLowerCase()).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%workstation%").replacement(placed.getType().toString().toLowerCase()).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info(event.getPlayer().getName() + " unoptimized a villager by breaking workstation: '" + placed.getType().toString().toLowerCase() + "'");
}
} }
} }

View File

@ -13,7 +13,7 @@ messages:
optimize-on-cooldown: optimize-on-cooldown:
- "<gray>You need to wait %time% until you can optimize this villager again." - "<gray>You need to wait %time% until you can optimize this villager again."
unoptimize-success: unoptimize-success:
- "<green>Successfully unoptimized villager by moving it off a %blocktype% block." - "<green>Successfully unoptimized %villagertype% villager by removing %blocktype%."
workstation: workstation:
optimize-success: optimize-success:
- "<green>%villagertype% villager successfully optimized using workstation block %blocktype%." - "<green>%villagertype% villager successfully optimized using workstation block %blocktype%."