diff --git a/build.gradle b/build.gradle index 38a5fb7c..1608ee45 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ plugins { id 'java-library' id "io.freefair.lombok" version "6.3.0" id "com.github.johnrengelman.shadow" version "7.1.2" + id "org.jetbrains.gradle.plugin.idea-ext" version "1.1.9" } version '1.16.0-1.19.2-1.21.3' @@ -99,6 +100,7 @@ allprojects { maven { url "https://repo.glaremasters.me/repository/bloodshot/" } maven { url "https://maven.enginehub.org/repo/" } maven { url "https://repo.oraxen.com/releases" } + maven { url "https://repo.alessiodp.com/releases" } maven { url 'https://jitpack.io' } } @@ -119,12 +121,14 @@ allprojects { implementation 'com.github.VolmitDev:Amulet:23.5.1' implementation 'com.github.VolmitDev:Chrono:22.9.10' implementation 'com.github.VolmitDev:Spatial:22.11.1' + implementation 'net.byteflux:libby-velocity:1.3.1' - implementation "io.papermc:paperlib:1.0.7" - implementation 'com.github.ben-manes.caffeine:caffeine:3.0.6' + compileOnly "io.papermc:paperlib:1.0.7" + compileOnly 'com.github.ben-manes.caffeine:caffeine:3.0.6' annotationProcessor 'systems.manifold:manifold-ext:' + manifoldVersion testAnnotationProcessor 'systems.manifold:manifold-ext:' + manifoldVersion implementation 'systems.manifold:manifold-rt:' + manifoldVersion + compileOnly 'io.lettuce:lettuce-core:6.5.1.RELEASE' //Random Api's compileOnly 'com.github.DeadSilenceIV:AdvancedChestsAPI:2.9-BETA' @@ -138,6 +142,7 @@ allprojects { compileOnly "com.github.TechFortress:GriefPrevention:16.18.1" implementation 'xyz.xenondevs:particle:1.8.1' implementation "com.frengor:ultimateadvancementapi-shadeable:2.4.2" + implementation 'com.jeff-media:custom-block-data:2.2.3' compileOnly 'com.griefdefender:api:2.1.0-SNAPSHOT' compileOnly 'io.netty:netty-all:4.1.68.Final' @@ -150,11 +155,17 @@ allprojects { compileOnly 'net.kyori:adventure-platform-bukkit:4.3.4' compileOnly 'it.unimi.dsi:fastutil:8.5.13' compileOnly "fr.skytasul:glowingentities:1.4.1" - implementation 'com.google.guava:guava:30.1-jre' + compileOnly 'com.google.guava:guava:30.1-jre' compileOnly fileTree(dir: 'libs', include: ['*.jar']) } } +dependencies { + compileOnly(project(":velocity")) { + transitive = false + } +} + /** * Configure Adapt for shading */ @@ -162,8 +173,10 @@ shadowJar { // minimize() VERSIONS.each { dependsOn(":version:${it.key}:build") - from(project(":version:${it.key}").layout.buildDirectory.file("libs/${it.key}.jar").get()) + from(project(":version:${it.key}").tasks.named("jar", Jar).get().archiveFile) } + dependsOn(":velocity:build") + from(project(":velocity").tasks.named("jar", Jar).get().archiveFile) append("plugin.yml") relocate 'manifold', 'com.volmit.adapt.util.manifold' @@ -171,11 +184,15 @@ shadowJar { relocate 'Fukkit.extensions', 'com.volmit.adapt.util.extensions' relocate 'Amulet.extensions', 'com.volmit.adapt.util.extensions' relocate 'com.fren_gor.ultimateAdvancementAPI', 'com.volmit.adapt.util.advancements' + relocate 'net.byteflux.libby', 'com.volmit.adapt.util.libby' + relocate 'com.jeff_media.customblockdata', 'com.volmit.util.customblocks' dependencies { include(dependency('systems.manifold:')) include(dependency('xyz.xenondevs:')) include(dependency('com.github.VolmitDev:')) include(dependency('com.frengor:ultimateadvancementapi-shadeable:')) + include(dependency('net.byteflux:')) + include(dependency('com.jeff-media:custom-block-data:')) } } @@ -279,3 +296,6 @@ if (JavaVersion.current() != JavaVersion.VERSION_1_8 && } } +idea.project.settings.taskTriggers { + afterSync(":velocity:generateTemplates") +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7e396aa7..611bba7f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name = 'Adapt' include( + ":velocity", ':version:v1_21_2', ':version:v1_21', ':version:v1_20_5', diff --git a/src/main/java/com/volmit/adapt/Adapt.java b/src/main/java/com/volmit/adapt/Adapt.java index 3bba0068..3a304cd4 100644 --- a/src/main/java/com/volmit/adapt/Adapt.java +++ b/src/main/java/com/volmit/adapt/Adapt.java @@ -19,6 +19,7 @@ package com.volmit.adapt; import art.arcane.amulet.io.FolderWatcher; +import com.jeff_media.customblockdata.CustomBlockData; import com.volmit.adapt.api.advancement.AdvancementManager; import com.volmit.adapt.api.data.WorldData; import com.volmit.adapt.api.potion.BrewingManager; @@ -29,6 +30,7 @@ import com.volmit.adapt.api.world.AdaptServer; import com.volmit.adapt.content.gui.SkillsGui; import com.volmit.adapt.content.protector.*; +import com.volmit.adapt.util.redis.RedisSync; import fr.skytasul.glowingentities.GlowingEntities; import com.volmit.adapt.util.*; import com.volmit.adapt.util.collection.KList; @@ -79,6 +81,8 @@ public class Adapt extends VolmitPlugin { @Getter private AdvancementManager manager; + @Getter + private RedisSync redisSync; private final KList postShutdown = new KList<>(); @@ -115,7 +119,9 @@ public void start() { if (AdaptConfig.get().isUseSql()) { sqlManager.establishConnection(); } + redisSync = new RedisSync(); startSim(); + CustomBlockData.registerListener(this); registerListener(new BrewingManager()); registerListener(Version.get()); setupMetrics(); diff --git a/src/main/java/com/volmit/adapt/AdaptConfig.java b/src/main/java/com/volmit/adapt/AdaptConfig.java index d68d730f..16a936f5 100644 --- a/src/main/java/com/volmit/adapt/AdaptConfig.java +++ b/src/main/java/com/volmit/adapt/AdaptConfig.java @@ -22,6 +22,7 @@ import com.volmit.adapt.api.xp.Curves; import com.volmit.adapt.util.IO; import com.volmit.adapt.util.JSONObject; +import com.volmit.adapt.util.redis.RedisConfig; import lombok.Getter; import lombok.Setter; import org.bukkit.Material; @@ -62,6 +63,7 @@ public class AdaptConfig { private boolean welcomeMessage = true; private boolean advancements = true; private boolean useSql = false; + private boolean useRedis = false; private int sqlSecondsCheckverify = 30; private boolean useEnchantmentTableParticleForActiveEffects = true; private boolean escClosesAllGuis = false; @@ -73,6 +75,7 @@ public class AdaptConfig { private boolean actionbarNotifyXp = true; private boolean actionbarNotifyLevel = true; private boolean unlearnAllButton = false; + private RedisConfig redis = new RedisConfig(); private SqlSettings sql = new SqlSettings(); private Protector protectorSupport = new Protector(); private Map> protectionOverrides = Map.of( diff --git a/src/main/java/com/volmit/adapt/api/recipe/AdaptRecipe.java b/src/main/java/com/volmit/adapt/api/recipe/AdaptRecipe.java index 4f9acb3d..e9849609 100644 --- a/src/main/java/com/volmit/adapt/api/recipe/AdaptRecipe.java +++ b/src/main/java/com/volmit/adapt/api/recipe/AdaptRecipe.java @@ -291,7 +291,7 @@ public ItemStack getResult() { public void register() { ShapedRecipe s = new ShapedRecipe(new NamespacedKey(Adapt.instance, getKey()), result); s.shape(shapes.toArray(new String[0])); - ingredients.forEach(i -> s.setIngredient(i.getCharacter(), i.getMaterial())); + ingredients.forEach(i -> s.setIngredient(i.getCharacter(), i.getChoice())); Bukkit.getServer().addRecipe(s); Adapt.verbose("Registered Shaped Crafting Recipe " + s.getKey()); } diff --git a/src/main/java/com/volmit/adapt/api/recipe/MaterialChar.java b/src/main/java/com/volmit/adapt/api/recipe/MaterialChar.java index 5248eeca..2645376f 100644 --- a/src/main/java/com/volmit/adapt/api/recipe/MaterialChar.java +++ b/src/main/java/com/volmit/adapt/api/recipe/MaterialChar.java @@ -21,10 +21,28 @@ import lombok.AllArgsConstructor; import lombok.Data; import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; @AllArgsConstructor @Data public class MaterialChar { private char character; - private Material material; + private RecipeChoice choice; + + public MaterialChar(char character, Tag tag) { + this.character = character; + this.choice = new RecipeChoice.MaterialChoice(tag); + } + + public MaterialChar(char character, Material... material) { + this.character = character; + this.choice = new RecipeChoice.MaterialChoice(material); + } + + public MaterialChar(char character, ItemStack... itemStack) { + this.character = character; + this.choice = new RecipeChoice.ExactChoice(itemStack); + } } diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptPlayer.java b/src/main/java/com/volmit/adapt/api/world/AdaptPlayer.java index 22f6fa9f..465c13f7 100644 --- a/src/main/java/com/volmit/adapt/api/world/AdaptPlayer.java +++ b/src/main/java/com/volmit/adapt/api/world/AdaptPlayer.java @@ -18,7 +18,6 @@ package com.volmit.adapt.api.world; -import com.google.gson.Gson; import com.volmit.adapt.Adapt; import com.volmit.adapt.AdaptConfig; import com.volmit.adapt.api.notification.AdvancementNotification; @@ -52,6 +51,7 @@ public class AdaptPlayer extends TickedObject { private long lastloc; private Vector velocity; private Location lastpos; + private long lastSeen = -1; public AdaptPlayer(Player p) { super("players", p.getUniqueId().toString(), 50); @@ -124,9 +124,10 @@ public PlayerSkillLine getSkillLine(String l) { @SneakyThrows private void save() { UUID uuid = player.getUniqueId(); - String data = new Gson().toJson(this.data); + String data = this.data.toJson(); if (AdaptConfig.get().isUseSql()) { + Adapt.instance.getRedisSync().publish(uuid, data); Adapt.instance.getSqlManager().updateData(uuid, data); } else { IO.writeAll(getPlayerDataFile(uuid), new JSONObject(data).toString(4)); @@ -136,10 +137,11 @@ private void save() { @SneakyThrows private void unSave() { UUID uuid = player.getUniqueId(); - String data = new Gson().toJson(new PlayerData()); + String data = new PlayerData().toJson(); unregister(); if (AdaptConfig.get().isUseSql()) { + Adapt.instance.getRedisSync().publish(uuid, data); Adapt.instance.getSqlManager().updateData(uuid, data); } else { IO.writeAll(getPlayerDataFile(uuid), new JSONObject(data).toString(4)); @@ -178,12 +180,27 @@ public void delete(UUID uuid) { }); } + public boolean shouldUnload() { + if (player.isOnline()) { + lastSeen = M.ms(); + return false; + } + + return lastSeen + 60_000 < System.currentTimeMillis(); + } + private PlayerData loadPlayerData() { boolean upload = false; if (AdaptConfig.get().isUseSql()) { + var opt = Adapt.instance.getRedisSync().cachedData(player.getUniqueId()); + if (opt.isPresent()) { + Adapt.verbose("Using cached data for player: " + player.getUniqueId()); + return opt.get(); + } + String sqlData = Adapt.instance.getSqlManager().fetchData(player.getUniqueId()); if (sqlData != null) { - return new Gson().fromJson(sqlData, PlayerData.class); + return PlayerData.fromJson(sqlData); } upload = true; } @@ -195,7 +212,7 @@ private PlayerData loadPlayerData() { if (upload) { Adapt.instance.getSqlManager().updateData(player.getUniqueId(), text); } - return new Gson().fromJson(text, PlayerData.class); + return PlayerData.fromJson(text); } catch (Throwable ignored) { Adapt.verbose("Failed to load player data for " + player.getName() + " (" + player.getUniqueId() + ")"); } diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptServer.java b/src/main/java/com/volmit/adapt/api/world/AdaptServer.java index 9af5e5bf..cefb465d 100644 --- a/src/main/java/com/volmit/adapt/api/world/AdaptServer.java +++ b/src/main/java/com/volmit/adapt/api/world/AdaptServer.java @@ -35,6 +35,7 @@ import com.volmit.adapt.content.item.KnowledgeOrb; import com.volmit.adapt.util.*; import lombok.Getter; +import lombok.NonNull; import lombok.SneakyThrows; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -55,7 +56,7 @@ public class AdaptServer extends TickedObject { private final ReentrantLock clearLock = new ReentrantLock(); - private final Map players = new ConcurrentHashMap<>(); + private final Map players = new ConcurrentHashMap<>(); @Getter private final List spatialTickets = new ArrayList<>(); @Getter @@ -112,12 +113,15 @@ public void takeSpatial(AdaptPlayer p) { public void join(Player p) { AdaptPlayer a = new AdaptPlayer(p); - players.put(p, a); + players.put(p.getUniqueId(), a); a.loggedIn(); } - public void quit(Player p) { - Optional.ofNullable(players.remove(p)).ifPresent(AdaptPlayer::unregister); + public void quit(UUID p) { + AdaptPlayer a = players.get(p); + if (a == null) return; + a.unregister(); + players.remove(p); } @Override @@ -175,7 +179,7 @@ public void on(PlayerJoinEvent e) { @EventHandler(priority = EventPriority.MONITOR) public void on(PlayerQuitEvent e) { Player p = e.getPlayer(); - quit(p); + quit(p.getUniqueId()); } @EventHandler @@ -205,7 +209,7 @@ public void onTick() { return; try { - players.keySet().removeIf(player -> !player.isOnline()); + players.values().removeIf(AdaptPlayer::shouldUnload); } finally { clearLock.unlock(); } @@ -236,11 +240,17 @@ public PlayerData peekData(UUID player) { return new PlayerData(); } + @NonNull + public Optional getPlayerData(@NonNull UUID uuid) { + return Optional.ofNullable(players.get(uuid)) + .map(AdaptPlayer::getData); + } + public AdaptPlayer getPlayer(Player p) { - return players.computeIfAbsent(p, player -> { + return players.computeIfAbsent(p.getUniqueId(), player -> { Adapt.warn("Failed to find AdaptPlayer for " + p.getName() + " (" + p.getUniqueId() + ")"); Adapt.warn("Loading new AdaptPlayer..."); - return new AdaptPlayer(player); + return new AdaptPlayer(p); }); } diff --git a/src/main/java/com/volmit/adapt/api/world/PlayerData.java b/src/main/java/com/volmit/adapt/api/world/PlayerData.java index 20069a3c..7fd3a9fd 100644 --- a/src/main/java/com/volmit/adapt/api/world/PlayerData.java +++ b/src/main/java/com/volmit/adapt/api/world/PlayerData.java @@ -18,6 +18,7 @@ package com.volmit.adapt.api.world; +import com.google.gson.Gson; import com.volmit.adapt.Adapt; import com.volmit.adapt.AdaptConfig; import com.volmit.adapt.api.notification.ActionBarNotification; @@ -43,6 +44,7 @@ @Data @NoArgsConstructor public class PlayerData { + private static final Gson GSON = new Gson(); private final Map skillLines = new ConcurrentHashMap<>(); private Map stats = new ConcurrentHashMap<>(); private String last = "none"; @@ -237,4 +239,12 @@ public PlayerSkillLine getSkillLine(String skillLine) { public void addWisdom() { wisdom++; } + + public String toJson() { + return GSON.toJson(this); + } + + public static PlayerData fromJson(String json) { + return GSON.fromJson(json, PlayerData.class); + } } diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectElevator.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectElevator.java new file mode 100644 index 00000000..94a7796b --- /dev/null +++ b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectElevator.java @@ -0,0 +1,388 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package com.volmit.adapt.content.adaptation.architect; + +import com.jeff_media.customblockdata.CustomBlockData; +import com.jeff_media.customblockdata.events.CustomBlockDataMoveEvent; +import com.jeff_media.customblockdata.events.CustomBlockDataRemoveEvent; +import com.volmit.adapt.Adapt; +import com.volmit.adapt.api.adaptation.SimpleAdaptation; +import com.volmit.adapt.api.recipe.AdaptRecipe; +import com.volmit.adapt.api.recipe.MaterialChar; +import com.volmit.adapt.util.*; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.*; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.VoxelShape; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class ArchitectElevator extends SimpleAdaptation { + private static final NamespacedKey ELEVATOR_KEY = new NamespacedKey(Adapt.instance, "elevator"); + private static final NamespacedKey TARGET_DOWN = new NamespacedKey(Adapt.instance, "target_down"); + private static final NamespacedKey TARGET_UP = new NamespacedKey(Adapt.instance, "target_up"); + + private static final int PARTICLE_COUNT = 20; + private static final float SOUND_VOLUME = 1f; + private static final float SOUND_PITCH = 1f; + + private final Set players = new HashSet<>(); + + public ArchitectElevator() { + super("architect-elevator"); + registerConfiguration(ArchitectElevator.Config.class); + setDescription(Localizer.dLocalize("architect", "elevator", "description")); + setDisplayName(Localizer.dLocalize("architect", "elevator", "name")); + setIcon(Material.HEAVY_WEIGHTED_PRESSURE_PLATE); + setInterval(988); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + + registerRecipe(AdaptRecipe.shaped() + .key("elevator") + .shape("XXX") + .shape("XYX") + .shape("XXX") + .ingredient(new MaterialChar('X', Tag.WOOL)) + .ingredient(new MaterialChar('Y', Material.ENDER_PEARL)) + .result(getElevatorItem()) + .build()); + } + + @Override + public void addStats(int level, Element v) { + + } + + public ItemStack getElevatorItem() { + ItemStack elevatorItem = CustomModel.get(Material.NOTE_BLOCK, "architect", "elevator", "item") + .toItemStack(); + ItemMeta meta = elevatorItem.getItemMeta(); + if (meta != null) { + meta.getPersistentDataContainer().set(ELEVATOR_KEY, PersistentDataType.BYTE, (byte) 0); + meta.setDisplayName(Localizer.dLocalize("items", "elevatorblock", "name")); + meta.setLore(List.of(Localizer.dLocalize("items", "elevatorblock", "usage1"), + Localizer.dLocalize("items", "elevatorblock", "usage2"), + Localizer.dLocalize("items", "elevatorblock", "usage3"))); + elevatorItem.setItemMeta(meta); + } + return elevatorItem; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerMoveEvent e) { + if (e.getTo() == null) return; + Player player = e.getPlayer(); + + if (!players.add(player.getUniqueId())) { + if (e.getFrom().getY() < e.getTo().getY() || player.isFlying()) + players.remove(player.getUniqueId()); + return; + } + + if (player.isFlying() || player.getVelocity().getY() <= 0 || e.getFrom().getY() >= e.getTo().getY()) + return; + + Block block = findElevator(player); + if (block == null) return; + handleElevatorMovement(block, player, false); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent event) { + if (!event.isSneaking()) return; + Player player = event.getPlayer(); + Block block = findElevator(player); + if (block == null) return; + handleElevatorMovement(block, player, true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPlaceEvent event) { + ItemMeta meta = event.getItemInHand().getItemMeta(); + if (meta == null || !meta.getPersistentDataContainer().has(ELEVATOR_KEY, PersistentDataType.BYTE)) + return; + int maxDistance = getMaxDistance(event.getPlayer()); + if (maxDistance <= 0) { + event.setCancelled(true); + return; + } + + Block block = event.getBlock(); + World world = block.getWorld(); + CustomBlockData data = new CustomBlockData(block, Adapt.instance); + data.set(ELEVATOR_KEY, PersistentDataType.INTEGER, maxDistance); + + int lowerDist = Math.min(block.getY() - world.getMinHeight(), maxDistance); + for (int d = 1; d <= lowerDist; d++) { + var lower = block.getRelative(BlockFace.DOWN, d); + if (checkElevator(lower, TARGET_UP, d)) { + data.set(TARGET_DOWN, PersistentDataType.INTEGER, d); + break; + } + } + + int upperDist = Math.min(world.getMaxHeight() - block.getY(), maxDistance); + for (int d = 1; d <= upperDist; d++) { + var upper = block.getRelative(BlockFace.UP, d); + if (checkElevator(upper, TARGET_DOWN, d)) { + data.set(TARGET_UP, PersistentDataType.INTEGER, d); + } + } + } + + public int getMaxDistance(Player player) { + int level = getLevel(player); + if (level == 0) return 0; + Config config = getConfig(); + return config.baseDistance * (level * config.multiplier); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(CustomBlockDataMoveEvent event) { + if (!event.getCustomBlockData().has(ELEVATOR_KEY)) return; + event.setCancelled(true); + + Event bukkit = event.getBukkitEvent(); + if (bukkit instanceof Cancellable cancellable) { + cancellable.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(BlockExplodeEvent event) { + event.blockList().removeIf(ArchitectElevator::isElevator); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(EntityExplodeEvent event) { + event.blockList().removeIf(ArchitectElevator::isElevator); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(CustomBlockDataRemoveEvent event) { + CustomBlockData data = event.getCustomBlockData(); + if (!data.has(ELEVATOR_KEY)) return; + Event bukkit = event.getBukkitEvent(); + if (!(bukkit instanceof BlockBreakEvent breakEvent)) { + if (bukkit instanceof Cancellable cancellable) + cancellable.setCancelled(true); + event.setCancelled(true); + return; + } + + breakEvent.setDropItems(false); + Block block = event.getBlock(); + World world = block.getWorld(); + Location location = block.getLocation(); + world.dropItemNaturally(location, getElevatorItem()); + + data.remove(ELEVATOR_KEY); + int y = block.getY(); + int lowerY = data.getOrDefault(TARGET_DOWN, PersistentDataType.INTEGER, 0); + int upperY = data.getOrDefault(TARGET_UP, PersistentDataType.INTEGER, 0); + data.remove(TARGET_DOWN); + data.remove(TARGET_UP); + + if (y - lowerY < world.getMinHeight()) + lowerY = 0; + + if (y + upperY > world.getMaxHeight()) + upperY = 0; + + if (lowerY != 0 && upperY != 0) { + Block lower = block.getRelative(BlockFace.DOWN, lowerY); + Block upper = block.getRelative(BlockFace.UP, upperY); + + boolean lowerElevator = isElevator(lower); + boolean upperElevator = isElevator(upper); + + if (lowerElevator && upperElevator) { + CustomBlockData lowerData = new CustomBlockData(lower, Adapt.instance); + CustomBlockData upperData = new CustomBlockData(upper, Adapt.instance); + + int dist = upperY + lowerY; + int lowerDist = lowerData.getOrDefault(ELEVATOR_KEY, PersistentDataType.INTEGER, 0); + int upperDist = upperData.getOrDefault(ELEVATOR_KEY, PersistentDataType.INTEGER, 0); + int maxDistance = Math.max(upperDist, lowerDist); + + if (dist <= maxDistance) { + lowerData.set(TARGET_UP, PersistentDataType.INTEGER, dist); + upperData.set(TARGET_DOWN, PersistentDataType.INTEGER, dist); + } else { + lowerData.remove(TARGET_UP); + upperData.remove(TARGET_DOWN); + } + } else if (lowerElevator) { + new CustomBlockData(lower, Adapt.instance) + .remove(TARGET_UP); + } else if (upperElevator) { + new CustomBlockData(upper, Adapt.instance) + .remove(TARGET_DOWN); + } + } else if (lowerY != 0) { + Block lower = block.getRelative(BlockFace.DOWN, lowerY); + + if (isElevator(lower)) { + new CustomBlockData(lower, Adapt.instance) + .remove(TARGET_UP); + } + } else if (upperY != 0) { + Block upper = block.getRelative(BlockFace.UP, upperY); + + if (isElevator(upper)) { + new CustomBlockData(upper, Adapt.instance) + .remove(TARGET_DOWN); + } + } + } + + @Nullable + private Block findElevator(Player player) { + Block base = player.getLocation().getBlock(); + for (int d = 1; d <= 2; d++) { + Block rel = base.getRelative(BlockFace.DOWN, d); + if (isElevator(rel)) + return rel; + } + return null; + } + + private boolean checkElevator(Block block, NamespacedKey key, int source) { + if (!isElevator(block)) + return false; + + new CustomBlockData(block, Adapt.instance) + .set(key, PersistentDataType.INTEGER, source); + return true; + } + + private void handleElevatorMovement(Block block, Player player, boolean down) { + if (!isElevator(block)) + return; + + CustomBlockData data = new CustomBlockData(block, Adapt.instance); + int distance = data.getOrDefault(down ? TARGET_DOWN : TARGET_UP, PersistentDataType.INTEGER, 0); + if (distance == 0) + return; + int targetY = block.getY() + (down ? -distance : distance); + if (targetY < block.getWorld().getMinHeight() || targetY > block.getWorld().getMaxHeight()) + return; + + Block target = block.getRelative(down ? BlockFace.DOWN : BlockFace.UP, distance); + if (!isElevator(target)) + return; + + var loc = player.getLocation(); + loc.setY(target.getY() + 1); + + if (!hasEnoughSpace(player, loc.getBlockY())) + return; + + teleportPlayer(player, loc); + } + + private static boolean isElevator(Block b) { + return b.getType() == Material.NOTE_BLOCK + && CustomBlockData.hasCustomBlockData(b, Adapt.instance) + && new CustomBlockData(b, Adapt.instance) + .has(ELEVATOR_KEY, PersistentDataType.INTEGER); + } + + private static boolean hasEnoughSpace(Player player, int targetY) { + BoundingBox box = player.getBoundingBox() + .shift(0, -player.getLocation().y(), 0) + .shift(0, targetY, 0); + + double maxX = Math.ceil(box.getMaxX()); + double maxY = Math.ceil(box.getMaxY()); + double maxZ = Math.ceil(box.getMaxZ()); + World world = player.getWorld(); + for (int x = (int) box.getMinX(); x <= maxX; x++) { + for (int z = (int) box.getMinZ(); z <= maxZ; z++) { + for (int y = (int) box.getMinY(); y <= maxY; y++) { + Block block = world.getBlockAt(x, y, z); + if (block.isPassable() || block.isLiquid()) + continue; + VoxelShape shape = block.getCollisionShape(); + box.shift(-x, -y, -z); + if (shape.overlaps(box)) + return false; + box.shift(x, y, z); + } + } + } + return true; + } + + private void teleportPlayer(Player p, Location l) { + playTeleportEffects(p); + p.teleport(l); + SoundPlayer.of(p.getWorld()).play(p, Sound.ENTITY_ENDERMAN_TELEPORT, SOUND_VOLUME, SOUND_PITCH); + playTeleportEffects(p); + } + + private void playTeleportEffects(Player p) { + p.getWorld().spawnParticle(Particle.PORTAL, p.getLocation(), PARTICLE_COUNT); + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + protected static class Config { + boolean permanent = false; + boolean enabled = true; + int baseDistance = 32; + int multiplier = 1; + int baseCost = 5; + int maxLevel = 4; + int initialCost = 1; + double costFactor = 0.40; + } +} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectFoundation.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectFoundation.java index f5456d07..73e7665a 100644 --- a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectFoundation.java +++ b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectFoundation.java @@ -69,7 +69,9 @@ public ArchitectFoundation() { @Override public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("architect", "foundation", "lore1") + (getBlockPower(getLevelPercent(level))) + C.GRAY + " " + Localizer.dLocalize("architect", "foundation", "lore2")); + v.addLore(C.GREEN + Localizer.dLocalize("architect", "foundation", "lore1") + + (getBlockPower(getLevelPercent(level))) + C.GRAY + " " + + Localizer.dLocalize("architect", "foundation", "lore2")); } @EventHandler(priority = EventPriority.HIGHEST) @@ -120,7 +122,7 @@ public void on(PlayerMoveEvent e) { blockPower.put(p, power); } - //prevent piston from moving blocks // Dupe fix + // prevent piston from moving blocks // Dupe fix @EventHandler(priority = EventPriority.HIGHEST) public void on(BlockPistonExtendEvent e) { if (e.isCancelled()) { @@ -134,7 +136,7 @@ public void on(BlockPistonExtendEvent e) { }); } - //prevent piston from pulling blocks // Dupe fix + // prevent piston from pulling blocks // Dupe fix @EventHandler(priority = EventPriority.HIGHEST) public void on(BlockPistonRetractEvent e) { if (e.isCancelled()) { @@ -148,7 +150,7 @@ public void on(BlockPistonRetractEvent e) { }); } - //prevent TNT from destroying blocks // Dupe fix + // prevent TNT from destroying blocks // Dupe fix @EventHandler(priority = EventPriority.HIGHEST) public void on(BlockExplodeEvent e) { if (e.isCancelled()) { @@ -160,7 +162,7 @@ public void on(BlockExplodeEvent e) { } } - //prevent block from being destroyed // Dupe fix + // prevent block from being destroyed // Dupe fix @EventHandler(priority = EventPriority.HIGHEST) public void on(BlockBreakEvent e) { if (activeBlocks.contains(e.getBlock())) { @@ -168,7 +170,7 @@ public void on(BlockBreakEvent e) { } } - //prevent Entities from destroying blocks // Dupe fix + // prevent Entities from destroying blocks // Dupe fix @EventHandler(priority = EventPriority.HIGHEST) public void on(EntityExplodeEvent e) { if (e.isCancelled()) { @@ -282,7 +284,6 @@ private boolean hasCooldown(Player i) { return cooldowns.containsKey(i); } - @Override public boolean isEnabled() { return getConfig().enabled; diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillArchitect.java b/src/main/java/com/volmit/adapt/content/skill/SkillArchitect.java index cde63b17..bea4f863 100644 --- a/src/main/java/com/volmit/adapt/content/skill/SkillArchitect.java +++ b/src/main/java/com/volmit/adapt/content/skill/SkillArchitect.java @@ -25,10 +25,7 @@ import com.volmit.adapt.api.skill.SimpleSkill; import com.volmit.adapt.api.world.AdaptPlayer; import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.architect.ArchitectFoundation; -import com.volmit.adapt.content.adaptation.architect.ArchitectGlass; -import com.volmit.adapt.content.adaptation.architect.ArchitectPlacement; -import com.volmit.adapt.content.adaptation.architect.ArchitectWirelessRedstone; +import com.volmit.adapt.content.adaptation.architect.*; import com.volmit.adapt.util.C; import com.volmit.adapt.util.CustomModel; import com.volmit.adapt.util.J; @@ -107,6 +104,7 @@ public SkillArchitect() { registerAdaptation(new ArchitectFoundation()); registerAdaptation(new ArchitectPlacement()); registerAdaptation(new ArchitectWirelessRedstone()); + registerAdaptation(new ArchitectElevator()); } @EventHandler(priority = EventPriority.MONITOR) diff --git a/src/main/java/com/volmit/adapt/util/Localizer.java b/src/main/java/com/volmit/adapt/util/Localizer.java index 7520acb1..0efd72a3 100644 --- a/src/main/java/com/volmit/adapt/util/Localizer.java +++ b/src/main/java/com/volmit/adapt/util/Localizer.java @@ -23,7 +23,6 @@ import com.google.gson.JsonParser; import com.volmit.adapt.Adapt; import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.util.secret.SecretSplash; import lombok.SneakyThrows; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -88,7 +87,8 @@ public static String dLocalize(String s1, String s2, String s3) { || jsonObj.get(s1).getAsJsonObject().get(s2).getAsJsonObject().get(s3) == null || jsonObj.get(s1).getAsJsonObject().get(s2).getAsJsonObject().get(s3).getAsString() == null) { - Adapt.verbose("Your Language File is missing the following key: " + s1 + "." + s2 + "." + s3); + String key = s1 + "." + s2 + "." + s3; + Adapt.verbose("Your Language File is missing the following key: " + key); Adapt.verbose("Loading English Language File FallBack"); JsonObject jsonObjFallback; @@ -101,10 +101,9 @@ public static String dLocalize(String s1, String s2, String s3) { || jsonObjFallback.get(s1).getAsJsonObject().get(s2) == null || jsonObjFallback.get(s1).getAsJsonObject().get(s2).getAsJsonObject().get(s3) == null || jsonObjFallback.get(s1).getAsJsonObject().get(s2).getAsJsonObject().get(s3).getAsString() == null) { - String f = SecretSplash.randomString7(); - Adapt.wordKey.put(s1 + s2 + s3, f); - Adapt.error("Your Fallback Language File is missing the following key: " + s1 + "." + s2 + "." + s3); - Adapt.verbose("New Assignement: " + f); + Adapt.wordKey.put(s1 + s2 + s3, key); + Adapt.error("Your Fallback Language File is missing the following key: " + key); + Adapt.verbose("New Assignement: " + key); Adapt.error("Please report this to the developer!"); } else { Adapt.wordKey.put(s1 + s2 + s3, jsonObjFallback.get(s1).getAsJsonObject().get(s2).getAsJsonObject().get(s3).getAsString()); diff --git a/src/main/java/com/volmit/adapt/util/redis/RedisSync.java b/src/main/java/com/volmit/adapt/util/redis/RedisSync.java new file mode 100644 index 00000000..3b21a880 --- /dev/null +++ b/src/main/java/com/volmit/adapt/util/redis/RedisSync.java @@ -0,0 +1,79 @@ +package com.volmit.adapt.util.redis; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.volmit.adapt.Adapt; +import com.volmit.adapt.AdaptConfig; +import com.volmit.adapt.api.world.PlayerData; +import com.volmit.adapt.util.redis.codec.Codec; +import com.volmit.adapt.util.redis.codec.DataMessage; +import com.volmit.adapt.util.redis.codec.DataRequest; +import com.volmit.adapt.util.redis.codec.Message; +import io.lettuce.core.RedisClient; +import io.lettuce.core.pubsub.api.reactive.ChannelMessage; +import io.lettuce.core.pubsub.api.reactive.RedisPubSubReactiveCommands; +import lombok.NonNull; +import lombok.extern.java.Log; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Log +public class RedisSync implements AutoCloseable { + private final RedisClient redisClient; + private final RedisPubSubReactiveCommands pubSub; + private final Cache dataCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(); + + public RedisSync() { + if (!AdaptConfig.get().isUseRedis() || !AdaptConfig.get().isUseSql()) { + this.redisClient = null; + this.pubSub = null; + return; + } + + this.redisClient = AdaptConfig.get().getRedis().createClient(); + this.pubSub = redisClient.connectPubSub(Codec.INSTANCE).reactive(); + pubSub.subscribe("Adapt:data").subscribe(); + pubSub.observeChannels().doOnNext(this::update).subscribe(); + } + + private void update(@NotNull ChannelMessage<@NotNull String, @Nullable Message> channelMessage) { + if (!channelMessage.getChannel().equals("Adapt:data")) return; + Message raw = channelMessage.getMessage(); + if (raw instanceof DataMessage message) { + Adapt.verbose("Received player data for " + message.uuid()); + dataCache.put(message.uuid(), message.json()); + } else if (raw instanceof DataRequest message) { + Adapt.instance.getAdaptServer() + .getPlayerData(message.uuid()) + .map(PlayerData::toJson) + .ifPresent(data -> publish(message.uuid(), data)); + } + } + + public void publish(@NonNull UUID uuid, @NonNull String playerData) { + if (pubSub == null) return; + Adapt.verbose("Publishing player data for " + uuid); + pubSub.publish("Adapt:data", new DataMessage(uuid, playerData)) + .subscribe() + .dispose(); + } + + @NonNull + public Optional cachedData(@NonNull UUID uuid) { + if (pubSub == null) return Optional.empty(); + return Optional.ofNullable(dataCache.getIfPresent(uuid)) + .map(PlayerData::fromJson); + } + + @Override + public void close() throws Exception { + if (redisClient != null) + redisClient.close(); + } +} diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/ReflectiveEvents.java b/src/main/java/com/volmit/adapt/util/reflect/events/ReflectiveEvents.java index a806abdf..11e55366 100644 --- a/src/main/java/com/volmit/adapt/util/reflect/events/ReflectiveEvents.java +++ b/src/main/java/com/volmit/adapt/util/reflect/events/ReflectiveEvents.java @@ -96,6 +96,10 @@ public static void register(Class eventInterface, String... cla } } + public static boolean exists(@NonNull Class eventInterface) { + return EVENTS.containsKey(eventInterface); + } + @Nullable private static HandlerList getHandlerList(Class parent) { while (parent != null) { diff --git a/src/main/resources/en_US.json b/src/main/resources/en_US.json index 8c220720..2e2d03d3 100644 --- a/src/main/resources/en_US.json +++ b/src/main/resources/en_US.json @@ -280,6 +280,12 @@ "boundsnowball": { "name": "Web Snare!", "usage1": "Throw to create a temporary web trap at the location" + }, + "elevatorblock": { + "name": "Elevator Block", + "usage1": "Jump to teleport up", + "usage2": "Shift to teleport down", + "usage3": "Minimum of 2 air blocks between the elevators" } }, "snippets": { @@ -474,6 +480,14 @@ } }, "architect": { + "elevator": { + "name": "Elevator", + "description": "This allows for you to build an elevator to teleport vertically fast!", + "lore1": "Unlocks elevator recipe: X=WOOL, Y=ENDER PEARL", + "lore2": "XXX", + "lore3": "XYX", + "lore4": "XXX" + }, "foundation": { "name": "Magic Foundation", "description": "This allows for you to sneak and place a temporary foundation beneath you!", diff --git a/src/main/resources/nl_NL.json b/src/main/resources/nl_NL.json index 2d451bf2..b47ca885 100644 --- a/src/main/resources/nl_NL.json +++ b/src/main/resources/nl_NL.json @@ -61,7 +61,7 @@ "description": "De beste goede jongen! (5 miljoen blokken)" }, "challenge_block_1k": { - "title": "Blokkerend!", + "title": "Nauwelijks geblokkeerd!", "description": "Blokkeer 1000 treffers" }, "challenge_block_5k": { @@ -129,7 +129,7 @@ "description": "Maak 5000 Voorwerpen" }, "challenge_craft_50k": { - "title": "Cervile Knutselaar!", + "title": "Onderdanige ambachtsman!", "description": "Maak 50.000 Voorwerpen" }, "challenge_craft_500k": { @@ -231,6 +231,34 @@ "challenge_pickaxe_5m": { "title": "Legendarische mijnwerker", "description": "Breek 5.000.000 blokken" + }, + "challenge_eat_100": { + "title": "Zoveel te eten!", + "description": "Eet meer dan 100 items!" + }, + "challenge_eat_1000": { + "title": "Onblusbare honger!", + "description": "Eet meer dan 1000 items!" + }, + "challenge_eat_10000": { + "title": "EEUWIGDURENDE HONGER!", + "description": "Eet meer dan 10.000 items!" + }, + "challenge_harvest_100": { + "title": "Volledige oogst", + "description": "Oogst meer dan 100 gewassen!" + }, + "challenge_harvest_1000": { + "title": "Grote oogst", + "description": "Oogst meer dan 1.000 gewassen!" + }, + "challenge_swim_1nm": { + "title": "Menselijke onderzeeër!", + "description": "Zwem 1 zeemijl (1.852 blokken)" + }, + "challenge_sneak_1k": { + "title": "Kniepijn", + "description": "Sluip over een kilometer (1.000 blokken)" } }, "items": { @@ -252,6 +280,12 @@ "boundsnowball": { "name": "Webstrik!", "usage1": "Gooi om een ​​tijdelijke web-valstrik op de locatie te maken" + }, + "elevatorblock": { + "name": "Liftblok", + "usage1": "Spring om omhoog te teleporteren", + "usage2": "Shift om naar beneden te teleporteren", + "usage3": "Minimaal 2 luchtblokken tussen de liften" } }, "snippets": { @@ -446,6 +480,14 @@ } }, "architect": { + "elevator": { + "name": "Lift", + "description": "Hiermee kun je een lift bouwen waarmee je snel verticaal kunt teleporteren!", + "lore1": "Ontgrendelt liftrecept: X=WOL, Y=ENDER PEARL", + "lore2": "XXX", + "lore3": "XYX", + "lore4": "XXX" + }, "foundation": { "name": "Magisch Fundament", "description": "Dit laat je een tijdelijk platform onder je plaatsen als je shift indrukt!", @@ -460,7 +502,7 @@ "wirelessredstone": { "name": "Redstone-afstandsbediening", "description": "Hierdoor kun je een Redstone-fakkel gebruiken om Redstone op afstand te schakelen!", - "lore1": "Doelwit + Redstone-fakkel + Enderperal = 1 Redstone-afstandsbediening" + "lore1": "Doelwit + Redstone-fakkel + Enderpearl = 1 Redstone-afstandsbediening" }, "placement": { "name": "Bouwers Staf", @@ -918,8 +960,14 @@ "description": "Wanneer je een blok breekt, wordt het item naar je inventaris geteleporteerd", "lore1": "Telkens wanneer een item uit een door jou gebroken blok wordt verwijderd, wordt het indien mogelijk in je inventaris geplaatst." }, + "silkspawner": { + "name": "Houweel Silk-Spawner", + "description": "Zorgt ervoor dat Spawners vallen als ze kapot gaan", + "lore1": "Maakt Spawners breekbaar door silk touch.", + "lore2": "Maakt Spawners breekbaar tijdens het sluipen." + }, "veinminer": { - "name": "Vemineermiddel", + "name": "Veinminer", "description": "Hiermee kun je blokken breken in een ader/cluster van vanille-erts", "lore1": "Sluip en mijn ERTSEN", "lore2": "bereik van adermijnbouw", @@ -997,6 +1045,11 @@ "description": "Krijg weerstand bij het gebruik van Ender-items en -vaardigheden", "lore1": "+ Passief: biedt weerstand wanneer je rift-vaardigheden of Ender-items gebruikt", "lore2": "NIET inclusief draagbare Enderchest, alleen dingen die u kunt consumeren" + }, + "visage": { + "name": "Rift Visage", + "description": "Voorkomt dat Endermannen agressief worden als je Enderpearls in je inventaris hebt.", + "lore1": "Endermannen worden niet agressief als je Enderpearls in je inventaris hebt." } }, "seaborn": { @@ -1051,6 +1104,12 @@ "name": "Sluipsnelheid", "description": "Verkrijg snelheid tijdens het sluipen", "lore1": "Sluipende snelheid" + }, + "enderveil": { + "name": "Enderveil", + "description": "Geen pompoenen meer nodig om Enderman-aanvallen te voorkomen", + "lore1": "Voorkom Enderman-aanvallen tijdens het sluipen", + "lore2": "Voorkom alle Enderman-aanvallen" } }, "sword": { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 4bc89d54..5ae57343 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -22,6 +22,8 @@ libraries: - net.kyori:adventure-text-minimessage:4.13.1 - it.unimi.dsi:fastutil:8.5.13 - fr.skytasul:glowingentities:1.4.1 + - io.lettuce:lettuce-core:6.5.1.RELEASE + - com.github.ben-manes.caffeine:caffeine:3.0.6 commands: adapt: {} diff --git a/src/main/resources/zh_CN.json b/src/main/resources/zh_CN.json index fd3d1b4d..2f9a7698 100644 --- a/src/main/resources/zh_CN.json +++ b/src/main/resources/zh_CN.json @@ -61,7 +61,7 @@ "description": "最棒的好孩子! (砍伐五百万个方块)" }, "challenge_block_1k": { - "title": "防御入门", + "title": "勉强挡住!", "description": "防御 1000 次攻击" }, "challenge_block_5k": { @@ -129,7 +129,7 @@ "description": "合成 5000 个物品" }, "challenge_craft_50k": { - "title": "成风之斫", + "title": "谄媚的工匠!", "description": "合成 50,000 个物品" }, "challenge_craft_500k": { @@ -231,6 +231,34 @@ "challenge_pickaxe_5m": { "title": "一段传奇", "description": "破坏 5,000,000 个方块" + }, + "challenge_eat_100": { + "title": "大快朵颐!", + "description": "吃掉 100 多种食物!" + }, + "challenge_eat_1000": { + "title": "暴食!", + "description": "吃掉 1,000 多种食物!" + }, + "challenge_eat_10000": { + "title": "饕餮!", + "description": "吃掉超过 10,000 种食物!" + }, + "challenge_harvest_100": { + "title": "丰年", + "description": "收获 100 多种农作物!" + }, + "challenge_harvest_1000": { + "title": "大丰收", + "description": "收获 1,000 多种农作物!" + }, + "challenge_swim_1nm": { + "title": "人体潜艇!", + "description": "游泳 1 海里(1,852 格)" + }, + "challenge_sneak_1k": { + "title": "膝盖疼痛", + "description": "潜行超过一公里(1,000 个方块)" } }, "items": { @@ -252,6 +280,12 @@ "boundsnowball": { "name": "蛛网陷阱!", "usage1": "投掷以在目标位置创建一个临时的蛛网陷阱" + }, + "elevatorblock": { + "name": "电梯方块", + "usage1": "跳跃传送至上方", + "usage2": "潜行传送至下方", + "usage3": "电梯之间至少有 2 个空气块" } }, "snippets": { @@ -264,6 +298,7 @@ "welcome": "欢迎!", "welcomeback": "欢迎回来!", "xpbonusfortime": "经验增益,持续", + "maxabilitypower": "最大能力数量", "unlockthisbyclicking": "右键该方块以解锁:", "back": "返回", "unlearnall": "忘却全部技能", @@ -340,7 +375,7 @@ "discovery": { "name": "探索", "icon": "⚛", - "description": "当洞察力提升时,你的思维便会愈发澄澈,探索更多的奥秘。" + "description": "随着你的感知范围扩大,你的思维就会逐渐展开,发现那些你从未发现过的事物。" }, "enchanting": { "name": "附魔", @@ -350,7 +385,7 @@ "excavation": { "name": "挖掘", "icon": "ᛳ", - "description": "挖大洞......" + "description": "在小小的花园里面挖呀挖呀挖……" }, "herbalism": { "name": "草药学", @@ -385,7 +420,7 @@ "seaborne": { "name": "激流", "icon": "🎣", - "description": "带着它吧,也许有一天你会见到由水组成的奇观。" + "description": "有了这项技能,你就可以领略水的奇妙。" }, "stealth": { "name": "隐形", @@ -410,12 +445,12 @@ "chronos": { "name": "时空", "icon": "⏳", - "description": "尝试着宇宙的钟上个发条,便可以感受到流动。若能把时钟弄坏,或许就能成为它。" + "description": "天人合一!" }, "unarmed": { "name": "搏击", "icon": "»", - "description": "赤手空拳并不意味着失去力量。" + "description": "没有武器并不意味着没有力量。" } }, "agility": { @@ -427,7 +462,7 @@ }, "superjump": { "name": "高跳", - "description": "一览众山小", + "description": "绝佳的高度优势。", "lore1": "最大跳跃高度", "lore2": "按下 潜行 + 跳跃 键进行高跳!" }, @@ -445,6 +480,14 @@ } }, "architect": { + "elevator": { + "name": "电梯", + "description": "这使您可以建造一个电梯来快速垂直传送!", + "lore1": "解锁电梯配方:X=任意羊毛,Y=末影珍珠", + "lore2": "XXX", + "lore3": "XYX", + "lore4": "XXX" + }, "foundation": { "name": "魔法基建", "description": "潜行时在脚下放置临时方块。", @@ -608,9 +651,9 @@ "name": "盔甲套组", "description": "在盔甲上绑定鞘翅", "lore1": "旅行必备品。", - "lore2": "飞行时动态合并或切换盔甲与鞘翅!", - "lore3": "要合并,在物品栏按住Shift拖拽合并物品到被合并物品上。", - "lore4": "要解绑盔甲上的物品,按住潜行并丢弃物品以拆解。", + "lore2": "动态合并并在飞行时动态切换铠甲/鞘翅!", + "lore3": "要合并,请按住 Shift 键单击库存中的某件物品,使其覆盖另一件物品。", + "lore4": "要解除盔甲的绑定,请潜行投掷该物品,它就会解除。", "lore5": "当盔甲耐久值至零时,合并到该盔甲上的所有物品都将丢失", "lore6": "我不再需要盔甲了,它让我太失望了..." } @@ -619,8 +662,8 @@ "deconstruction": { "name": "分解器", "description": "分解方块或物品,回收部分合成时的材料。", - "lore1": "将 被分解物品 + 剪刀", - "lore2": "放入锻造台进行回收。" + "lore1": "将物品丢弃在地上。", + "lore2": "然后潜行并用剪刀右键点击" }, "xp": { "name": "熟能生巧", @@ -734,13 +777,13 @@ "omnitool": { "name": "OMNI - T.O.O.L.", "description": "Tackle设计的豪华多功能工具钳。", - "lore1": "这也许是最强大的设计。", + "lore1": "可能是其中最强大的一个,可以让你", "lore2": "可以根据实际需要合并或拆解工具。", - "lore3": "合并:按住潜行键,在物品栏中用预合并的物品点击被合并的物品。", - "lore4": "拆解:手持预拆解的物品,按住潜行键丢弃以拆解。", - "lore5": "注意:如果此工具钳套件中任何一个物品耐久度至零,该工具钳将报废!", - "lore6": "个可合并物品。", - "lore7": "有时候你需要五、六种物品在同一个工具钳里,或许仅仅需要一个。" + "lore3": "要合并,请按住 Shift 键单击库存中的某件物品,然后使其覆盖另一件物品。", + "lore4": "要解除工具绑定,请潜行放下该物品,它就会解除。", + "lore5": "在这个皮套工具里你不能弄坏工具,但你也不能使用已损坏的工具", + "lore6": "可合并项目总数。", + "lore7": "您可以使用五六种工具,或者只使用一种!" } }, "herbalism": { @@ -917,6 +960,12 @@ "description": "当你击杀生物/破坏某方块,它的掉落物将会自动进入背包。", "lore1": "如果可以,击杀生物/破坏某方块的掉落物将会自动进入背包。" }, + "silkspawner": { + "name": "精准刷怪笼采集", + "description": "刷怪笼被破坏时会掉落", + "lore1": "使刷怪笼可被精准采集的稿子破坏时掉落。", + "lore2": "使得刷怪笼在潜行时可被破坏。" + }, "veinminer": { "name": "连锁挖矿", "description": "允许你遇到原版矿物的矿脉时进行连锁挖矿。", @@ -996,6 +1045,11 @@ "description": "当使用末影物品或技能时,给予抗性提升", "lore1": "+ 被动效果:当你使用末影物品或技能时,给予抗性提升效果。", "lore2": "不包含便捷式末影箱。只对实际消耗的物品有效。" + }, + "visage": { + "name": "裂隙面容", + "description": "如果您的库存中有末影珍珠,则可以防止末影人变得具有攻击性。", + "lore1": "如果你的库存中有末影珍珠,末影人将不会变得具有攻击性。" } }, "seaborn": { @@ -1050,6 +1104,12 @@ "name": "潜行加速", "description": "当潜行时给予速度效果", "lore1": "潜行速度" + }, + "enderveil": { + "name": "末影面纱", + "description": "不再使用南瓜来避免末影人的攻击", + "lore1": "潜行时防止末影人的攻击", + "lore2": "阻止所有末影人攻击" } }, "sword": { @@ -1101,7 +1161,7 @@ "globe": { "name": "痛苦之珠", "description": "根据你周围敌人的数量来分配你所造成的伤害!", - "lore1": "你周围的敌人越多,你对他们每个人造成的伤害就越少", + "lore1": "你周围的敌人越多,你对每个敌人造成的伤害就越少", "lore2": "范围:", "lore3": "对所有实体增加伤害:" }, diff --git a/velocity/build.gradle b/velocity/build.gradle new file mode 100644 index 00000000..bc9aecb4 --- /dev/null +++ b/velocity/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'java' +} + +dependencies { + compileOnly("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") + annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") +} + +def templateSource = file("src/main/templates") +def templateDest = layout.buildDirectory.dir("generated/sources/templates") +def generateTemplates = tasks.register("generateTemplates", Copy) { + def props = [ + "id": rootProject.name.toLowerCase(), + "name": rootProject.name, + "version": rootProject.version, + ] + inputs.properties(props) + + from(templateSource) + into(templateDest) + rename { "com/volmit/adapt/$it" } + expand(props) +} + +sourceSets.main { + java.srcDir(generateTemplates.map { it.outputs }) +} \ No newline at end of file diff --git a/velocity/src/main/java/com/volmit/adapt/AdaptVelocity.java b/velocity/src/main/java/com/volmit/adapt/AdaptVelocity.java new file mode 100644 index 00000000..d7405d47 --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/AdaptVelocity.java @@ -0,0 +1,130 @@ +package com.volmit.adapt; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.inject.Inject; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyReloadEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import com.volmit.adapt.util.redis.VelocityConfig; +import net.byteflux.libby.Library; +import net.byteflux.libby.VelocityLibraryManager; +import org.slf4j.Logger; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; + +@Plugin(id = BuildConstants.ID, + name = BuildConstants.NAME, + version = BuildConstants.VERSION, + authors = {"NextdoorPsycho", "Cyberpwn", "Vatuu"}) +public class AdaptVelocity { + private static final Gson GSON = new GsonBuilder() + .setLenient() + .disableHtmlEscaping() + .setPrettyPrinting() + .create(); + + private final VelocityLibraryManager libby; + private final Logger logger; + private final ProxyServer proxy; + private final File configFile; + private RedisHandler handler; + + @Inject + public AdaptVelocity(Logger logger, + ProxyServer proxy, + PluginManager pluginManager, + @DataDirectory Path dataFolder) { + libby = new VelocityLibraryManager<>(logger, dataFolder, pluginManager, this); + libby.addMavenCentral(); + + this.logger = logger; + this.proxy = proxy; + this.configFile = dataFolder.resolve("config.yml").toFile(); + } + + @Subscribe + public void onInitialize(ProxyInitializeEvent event) throws IOException { + netty("common"); + netty("handler"); + netty("buffer"); + netty("codec"); + netty("resolver"); + netty("transport"); + netty("transport-native-unix-common"); + dependency("io.lettuce:lettuce-core:6.5.1.RELEASE"); + dependency("io.projectreactor:reactor-core:3.6.6"); + dependency("org.reactivestreams:reactive-streams:1.0.4"); + + load(); + } + + @Subscribe + public void onReload(ProxyReloadEvent event) throws Exception { + if (handler != null) { + proxy.getEventManager().unregisterListener(this, handler); + handler.close(); + handler = null; + } + + load(); + } + + @Subscribe + public void onShutdown(ProxyShutdownEvent event) throws Exception { + if (this.handler != null) { + proxy.getEventManager().unregisterListener(this, handler); + handler.close(); + handler = null; + } + } + + private void load() throws IOException { + if (!configFile.exists()) { + logger.info("Config file does not exist. Creating one."); + if (!configFile.getParentFile().exists() && !configFile.getParentFile().mkdirs()) + throw new IOException("Unable to create directory " + configFile.getParentFile()); + try (FileWriter writer = new FileWriter(configFile)) { + GSON.toJson(new VelocityConfig(), writer); + } + return; + } + + VelocityConfig config; + try (FileReader reader = new FileReader(configFile)) { + config = GSON.fromJson(reader, VelocityConfig.class); + } + + this.handler = new RedisHandler(config.isDebug(), config.createClient()); + proxy.getEventManager().register(this, handler); + } + + private void dependency(String dependency) { + String[] part = dependency.split(":", 3); + if (part.length != 3) throw new IllegalArgumentException("Invalid dependency: " + dependency); + libby.loadLibrary(Library.builder() + .id(part[1]) + .groupId(part[0]) + .artifactId(part[1]) + .version(part[2]) + .build()); + } + + private void netty(String artifactId) { + libby.loadLibrary(Library.builder() + .id("netty-" + artifactId) + .groupId("io.netty") + .artifactId("netty-" + artifactId) + .version("4.1.115.Final") + .build()); + } +} diff --git a/velocity/src/main/java/com/volmit/adapt/RedisHandler.java b/velocity/src/main/java/com/volmit/adapt/RedisHandler.java new file mode 100644 index 00000000..1338390d --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/RedisHandler.java @@ -0,0 +1,47 @@ +package com.volmit.adapt; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.ServerPreConnectEvent; +import com.volmit.adapt.util.redis.codec.Codec; +import com.volmit.adapt.util.redis.codec.DataRequest; +import com.volmit.adapt.util.redis.codec.Message; +import io.lettuce.core.RedisClient; +import io.lettuce.core.pubsub.api.reactive.RedisPubSubReactiveCommands; +import lombok.extern.java.Log; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +@Log(topic = "adapt") +public class RedisHandler implements AutoCloseable { + private static final String SENDING = "Sending data request to servers for player %s(%s)"; + private static final String SENT = "Sent data request to servers for player %s(%s) to %s servers"; + + private final boolean debug; + private final RedisClient redisClient; + private final RedisPubSubReactiveCommands pubSub; + + public RedisHandler(boolean debug, RedisClient redisClient) { + this.debug = debug; + this.redisClient = redisClient; + this.pubSub = redisClient.connectPubSub(Codec.INSTANCE).reactive(); + } + + @Subscribe(async = false) + public void onServerPreConnect(ServerPreConnectEvent event) { + var player = event.getPlayer(); + debug(SENDING, player.getUsername(), player.getUniqueId()); + Long received = pubSub.publish("Adapt:data", new DataRequest(player.getUniqueId())) + .block(Duration.of(3, ChronoUnit.SECONDS)); + if (received == null) return; + debug(SENT, player.getUsername(), player.getUniqueId(), received); + } + + private void debug(String message, Object... args) { + if (debug) log.info(message.formatted(args)); + } + + public void close() throws Exception { + redisClient.close(); + } +} diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/RedisConfig.java b/velocity/src/main/java/com/volmit/adapt/util/redis/RedisConfig.java new file mode 100644 index 00000000..ff47e4cc --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/util/redis/RedisConfig.java @@ -0,0 +1,27 @@ +package com.volmit.adapt.util.redis; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.StaticCredentialsProvider; +import lombok.Data; +import lombok.NonNull; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +@Data +@Accessors(chain = true) +public class RedisConfig { + private @NonNull String host = "127.0.0.1"; + private int port = 6379; + private @Nullable String username = ""; + private @Nullable String password = ""; + + @Contract(" -> new") + public RedisClient createClient() { + var uri = RedisURI.create(host, port); + if ((username != null && !username.isEmpty()) || (password != null && !password.isEmpty())) + uri.setCredentialsProvider(new StaticCredentialsProvider(username, password != null ? password.toCharArray() : null)); + return RedisClient.create(uri); + } +} diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/VelocityConfig.java b/velocity/src/main/java/com/volmit/adapt/util/redis/VelocityConfig.java new file mode 100644 index 00000000..1679e685 --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/util/redis/VelocityConfig.java @@ -0,0 +1,10 @@ +package com.volmit.adapt.util.redis; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class VelocityConfig extends RedisConfig { + private boolean debug = false; +} diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/ByteBufferInputStream.java b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/ByteBufferInputStream.java new file mode 100644 index 00000000..bfecdb13 --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/ByteBufferInputStream.java @@ -0,0 +1,44 @@ +package com.volmit.adapt.util.redis.codec; + +import lombok.AllArgsConstructor; +import lombok.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +@AllArgsConstructor +public class ByteBufferInputStream extends InputStream { + private final @NonNull ByteBuffer buffer; + + @Override + public int read() throws IOException { + return buffer.get() & 0xFF; + } + + @Override + public int read(byte @NotNull [] b) throws IOException { + buffer.get(b, 0, b.length); + return b.length; + } + + @Override + public int read(byte @NotNull [] b, int off, int len) throws IOException { + buffer.get(b, off, len); + return len; + } + + @Override + public byte @NotNull [] readNBytes(int len) { + byte[] b = new byte[len]; + buffer.get(b, 0, len); + return b; + } + + @Override + public int readNBytes(byte[] b, int off, int len) { + buffer.get(b, off, len); + return len; + } +} diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Codec.java b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Codec.java new file mode 100644 index 00000000..dd980417 --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Codec.java @@ -0,0 +1,75 @@ +package com.volmit.adapt.util.redis.codec; + +import com.google.common.io.ByteStreams; +import io.lettuce.core.codec.RedisCodec; +import lombok.NonNull; +import lombok.extern.java.Log; +import manifold.rt.api.util.Pair; +import org.jetbrains.annotations.Contract; + +import java.io.*; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.logging.Level; + +import static io.lettuce.core.codec.StringCodec.UTF8; + +@Log +public final class Codec implements RedisCodec { + public static final Codec INSTANCE = new Codec() + .register(DataRequest.class, DataRequest::decode) + .register(DataMessage.class, DataMessage::decode); + + private final Map, Pair, Integer>> types = new HashMap<>(); + private final List> messages = new ArrayList<>(); + + private Codec() {} + + @Override + public String decodeKey(ByteBuffer bytes) { + return UTF8.decodeKey(bytes); + } + + @Override + public Message decodeValue(ByteBuffer bytes) { + try (var in = new DataInputStream(new ByteBufferInputStream(bytes))){ + int id = in.readInt(); + if (id < 0 || id >= messages.size()) return null; + return messages.get(id).decode(in); + } catch (IOException e) { + log.log(Level.SEVERE, "Error decoding message", e); + return null; + } + } + + @Override + public ByteBuffer encodeKey(String key) { + return UTF8.encodeKey(key); + } + + @Override + public ByteBuffer encodeValue(Message value) { + try { + var out = ByteStreams.newDataOutput(); + int id = Optional.ofNullable(types.get(value.getClass()).getSecond()) + .orElse(-1); + out.writeInt(id); + value.encode(out); + return ByteBuffer.wrap(out.toByteArray()); + } catch (IOException e) { + log.log(Level.SEVERE, "Error encoding message", e); + return ByteBuffer.allocate(4) + .putInt(-1); + } + } + + @Contract("_, _ -> this") + public Codec register(@NonNull Class type, @NonNull Message.Decoder decoder) { + if (types.containsKey(type)) + throw new IllegalArgumentException("Type " + type + " already registered"); + int id = messages.size(); + messages.add(decoder); + types.put(type, Pair.make(decoder, id)); + return this; + } +} diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataMessage.java b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataMessage.java new file mode 100644 index 00000000..c588473c --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataMessage.java @@ -0,0 +1,30 @@ +package com.volmit.adapt.util.redis.codec; + +import lombok.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.UUID; + +public record DataMessage( + @NonNull UUID uuid, + @NonNull String json +) implements Message { + + @Override + public void encode(@NotNull DataOutput output) throws IOException { + output.writeLong(uuid.getMostSignificantBits()); + output.writeLong(uuid.getLeastSignificantBits()); + output.writeUTF(json); + } + + @NotNull + public static DataMessage decode(@NotNull DataInput input) throws IOException { + return new DataMessage( + new UUID(input.readLong(), input.readLong()), + input.readUTF() + ); + } +} diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataRequest.java b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataRequest.java new file mode 100644 index 00000000..f8baca76 --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataRequest.java @@ -0,0 +1,24 @@ +package com.volmit.adapt.util.redis.codec; + +import lombok.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.UUID; + +public record DataRequest( + @NonNull UUID uuid +) implements Message { + @Override + public void encode(@NotNull DataOutput output) throws IOException { + output.writeLong(uuid.getMostSignificantBits()); + output.writeLong(uuid.getLeastSignificantBits()); + } + + @NonNull + public static DataRequest decode(@NotNull DataInput input) throws IOException { + return new DataRequest(new UUID(input.readLong(), input.readLong())); + } +} diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Message.java b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Message.java new file mode 100644 index 00000000..6ba1a9e2 --- /dev/null +++ b/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Message.java @@ -0,0 +1,18 @@ +package com.volmit.adapt.util.redis.codec; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public interface Message { + void encode(@NotNull DataOutput output) throws IOException; + + @FunctionalInterface + interface Decoder { + @Nullable + T decode(@NotNull DataInput input) throws IOException; + } +} diff --git a/velocity/src/main/templates/BuildConstants.java b/velocity/src/main/templates/BuildConstants.java new file mode 100644 index 00000000..cbed8b0f --- /dev/null +++ b/velocity/src/main/templates/BuildConstants.java @@ -0,0 +1,8 @@ +package com.volmit.adapt; + +// The constants are replaced before compilation +public class BuildConstants { + public static final String ID = "${id}"; + public static final String NAME = "${name}"; + public static final String VERSION = "${version}"; +}