diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java b/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java index fe69859c1..923a33260 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java @@ -58,6 +58,7 @@ public class Movecraft extends JavaPlugin { private WorldHandler worldHandler; private SmoothTeleport smoothTeleport; private AsyncManager asyncManager; + private AbstractSignListener abstractSignListener; private WreckManager wreckManager; public static synchronized Movecraft getInstance() { @@ -132,6 +133,12 @@ public void onEnable() { getLogger().warning("Falling back to bukkit teleportation provider."); } } + + final Class signListenerClass = Class.forName("net.countercraft.movecraft.compat." + WorldHandler.getPackageName(minecraftVersion) + ".SignListener"); + if (AbstractSignListener.class.isAssignableFrom(signListenerClass)) { + abstractSignListener = (AbstractSignListener) signListenerClass.getConstructor().newInstance(); + getServer().getPluginManager().registerEvents(abstractSignListener, this); + } } catch (final Exception e) { e.printStackTrace(); @@ -209,37 +216,60 @@ public void onEnable() { getCommand("crafttype").setExecutor(new CraftTypeCommand()); getCommand("craftinfo").setExecutor(new CraftInfoCommand()); + // Naming scheme: If it has parameters, append a double colon except if it is a subcraft + // Parameters follow on the following lines getServer().getPluginManager().registerEvents(new BlockListener(), this); getServer().getPluginManager().registerEvents(new PlayerListener(), this); getServer().getPluginManager().registerEvents(new ChunkManager(), this); - getServer().getPluginManager().registerEvents(new AscendSign(), this); - getServer().getPluginManager().registerEvents(new CraftSign(), this); - getServer().getPluginManager().registerEvents(new CruiseSign(), this); - getServer().getPluginManager().registerEvents(new DescendSign(), this); - getServer().getPluginManager().registerEvents(new HelmSign(), this); - getServer().getPluginManager().registerEvents(new MoveSign(), this); - getServer().getPluginManager().registerEvents(new NameSign(), this); - getServer().getPluginManager().registerEvents(new PilotSign(), this); - getServer().getPluginManager().registerEvents(new RelativeMoveSign(), this); - getServer().getPluginManager().registerEvents(new ReleaseSign(), this); - getServer().getPluginManager().registerEvents(new RemoteSign(), this); - getServer().getPluginManager().registerEvents(new SpeedSign(), this); - getServer().getPluginManager().registerEvents(new SubcraftRotateSign(), this); - getServer().getPluginManager().registerEvents(new TeleportSign(), this); - getServer().getPluginManager().registerEvents(new ScuttleSign(), this); + //getServer().getPluginManager().registerEvents(new AscendSign(), this); + AbstractMovecraftSign.register("Ascend:", new AscendSign()); + //getServer().getPluginManager().registerEvents(new CruiseSign(), this); + AbstractMovecraftSign.register("Cruise:", new CruiseSign()); + //getServer().getPluginManager().registerEvents(new DescendSign(), this); + AbstractMovecraftSign.register("Descend:", new DescendSign()); + //getServer().getPluginManager().registerEvents(new HelmSign(), this); + AbstractMovecraftSign.register("[Helm]", new HelmSign()); + AbstractMovecraftSign.register(HelmSign.PRETTY_HEADER, new HelmSign()); + //getServer().getPluginManager().registerEvents(new MoveSign(), this); + AbstractMovecraftSign.register("Move:", new MoveSign()); + //getServer().getPluginManager().registerEvents(new NameSign(), this); + AbstractMovecraftSign.register("Name:", new NameSign()); + //getServer().getPluginManager().registerEvents(new PilotSign(), this); + AbstractMovecraftSign.register("Pilot:", new PilotSign()); + //getServer().getPluginManager().registerEvents(new RelativeMoveSign(), this); + AbstractMovecraftSign.register("RMove:", new RelativeMoveSign()); + //getServer().getPluginManager().registerEvents(new ReleaseSign(), this); + AbstractMovecraftSign.register("Release", new ReleaseSign()); + //getServer().getPluginManager().registerEvents(new RemoteSign(), this); + AbstractMovecraftSign.register("Remote Sign", new RemoteSign()); + //getServer().getPluginManager().registerEvents(new SpeedSign(), this); + AbstractMovecraftSign.register("Speed:", new SpeedSign()); + AbstractMovecraftSign.register("Status:", new StatusSign()); + AbstractMovecraftSign.register("Contacts:", new ContactsSign()); + //getServer().getPluginManager().registerEvents(new SubcraftRotateSign(), this); + AbstractMovecraftSign.register("Subcraft Rotate", new SubcraftRotateSign(CraftManager.getInstance()::getCraftTypeFromString, Movecraft::getInstance)); + //getServer().getPluginManager().registerEvents(new TeleportSign(), this); + AbstractMovecraftSign.register("Teleport:", new TeleportSign()); + //getServer().getPluginManager().registerEvents(new ScuttleSign(), this); + AbstractMovecraftSign.register("Scuttle", new ScuttleSign()); getServer().getPluginManager().registerEvents(new CraftPilotListener(), this); getServer().getPluginManager().registerEvents(new CraftReleaseListener(), this); + // Moved to compat section! + //getServer().getPluginManager().registerEvents(new SignListener(), this); + + AbstractMovecraftSign.registerCraftPilotSigns(CraftManager.getInstance().getCraftTypes(), CraftPilotSign::new); var contactsManager = new ContactsManager(); contactsManager.runTaskTimerAsynchronously(this, 0, 20); getServer().getPluginManager().registerEvents(contactsManager, this); - getServer().getPluginManager().registerEvents(new ContactsSign(), this); + //getServer().getPluginManager().registerEvents(new ContactsSign(), this); + getServer().getPluginManager().registerEvents(new CraftTypeListener(), this); getCommand("contacts").setExecutor(new ContactsCommand()); var statusManager = new StatusManager(); statusManager.runTaskTimerAsynchronously(this, 0, 1); getServer().getPluginManager().registerEvents(statusManager, this); - getServer().getPluginManager().registerEvents(new StatusSign(), this); + //getServer().getPluginManager().registerEvents(new StatusSign(), this); logger.info("[V " + getDescription().getVersion() + "] has been enabled."); } @@ -337,6 +367,8 @@ public AsyncManager getAsyncManager() { return asyncManager; } + public AbstractSignListener getAbstractSignListener() {return abstractSignListener;} + public @NotNull WreckManager getWreckManager(){ return wreckManager; } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/commands/MovecraftCommand.java b/Movecraft/src/main/java/net/countercraft/movecraft/commands/MovecraftCommand.java index 6bb739cbd..1ceea602f 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/commands/MovecraftCommand.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/commands/MovecraftCommand.java @@ -3,6 +3,8 @@ import net.countercraft.movecraft.Movecraft; import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.localisation.I18nSupport; +import net.countercraft.movecraft.sign.AbstractMovecraftSign; +import net.countercraft.movecraft.sign.CraftPilotSign; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java index 83f52d845..a88978759 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java @@ -3,107 +3,94 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.type.CraftType; -import net.countercraft.movecraft.events.CraftDetectEvent; -import net.countercraft.movecraft.events.SignTranslateEvent; -import org.bukkit.ChatColor; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import net.countercraft.movecraft.sign.AbstractInformationSign; +import net.countercraft.movecraft.sign.AbstractSignListener; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class ContactsSign implements Listener { - private static final String HEADER = "Contacts:"; +import java.util.List; - @EventHandler - public void onCraftDetect(@NotNull CraftDetectEvent event) { - World world = event.getCraft().getWorld(); - for (MovecraftLocation location : event.getCraft().getHitBox()) { - var block = location.toBukkit(world).getBlock(); - if (!Tag.SIGNS.isTagged(block.getType())) - continue; +public class ContactsSign extends AbstractInformationSign { - BlockState state = block.getState(); - if (!(state instanceof Sign sign)) - continue; + protected final int MAX_DISTANCE_COLOR_RED = 64 * 64; + protected final int MAX_DISTANCE_COLOR_YELLOW = 128 * 128; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) - continue; - - sign.setLine(1, ""); - sign.setLine(2, ""); - sign.setLine(3, ""); - sign.update(); - } - } - - @EventHandler - public final void onSignTranslateEvent(@NotNull SignTranslateEvent event) { - if (!ChatColor.stripColor(event.getLine(0)).equalsIgnoreCase(HEADER)) - return; + protected @NotNull Component contactsLine(@NotNull Craft base, @NotNull Craft target) { + MovecraftLocation baseCenter = base.getHitBox().getMidPoint(); + MovecraftLocation targetCenter = target.getHitBox().getMidPoint(); + int distanceSquared = baseCenter.distanceSquared(targetCenter); - Craft base = event.getCraft(); - int line = 1; - for (Craft target : base.getDataTag(Craft.CONTACTS)) { - if (line > 3) - break; + String craftTypeName = target.getType().getStringProperty(CraftType.NAME); + if (craftTypeName.length() > 9) + craftTypeName = craftTypeName.substring(0, 7); - event.setLine(line++, contactsLine(base, target)); + Style style = STYLE_COLOR_GREEN; + if (distanceSquared <= MAX_DISTANCE_COLOR_RED) { + style = STYLE_COLOR_RED; } - while (line <= 3) { - event.setLine(line++, ""); + else if (distanceSquared <= MAX_DISTANCE_COLOR_YELLOW) { + style = STYLE_COLOR_YELLOW; } - } - - private static @NotNull String contactsLine(@NotNull Craft base, @NotNull Craft target) { - MovecraftLocation baseCenter = base.getHitBox().getMidPoint(); - MovecraftLocation targetCenter = target.getHitBox().getMidPoint(); - int distanceSquared = baseCenter.distanceSquared(targetCenter); - String result = ChatColor.BLUE + target.getType().getStringProperty(CraftType.NAME); - if (result.length() > 9) - result = result.substring(0, 7); + Component result = Component.text(craftTypeName + " ").style(style); - result += " " + (int) Math.sqrt(distanceSquared); int diffX = baseCenter.getX() - targetCenter.getX(); int diffZ = baseCenter.getZ() - targetCenter.getZ(); + String directionStr = "" + (int) Math.sqrt(distanceSquared); if (Math.abs(diffX) > Math.abs(diffZ)) { if (diffX<0) { - result +=" E"; + directionStr +=" E"; } else { - result +=" W"; + directionStr +=" W"; } } else { if (diffZ<0) { - result +=" S"; + directionStr +=" S"; } else { - result +=" N"; + directionStr +=" N"; } } + result = result.append(Component.text(directionStr).style(STYLE_COLOR_WHITE)); return result; } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClickEvent(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) - return; + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { + player.performCommand("contacts"); + + return true; + } + + @Override + protected @Nullable Component getUpdateString(int lineIndex, Component oldData, Craft craft) { + Craft contact = null; + List contacts = craft.getDataTag(Craft.CONTACTS); + if (contacts.isEmpty() || contacts.size() < lineIndex) { + return EMPTY; + } + contact = contacts.get(lineIndex); - Block block = event.getClickedBlock(); - if (block == null) - return; - if (!(block.getState() instanceof Sign sign)) - return; + return contactsLine(craft, contact); + } + + @Override + protected @Nullable Component getDefaultString(int lineIndex, Component oldComponent) { + return EMPTY; + } + + @Override + protected void performUpdate(Component[] newComponents, AbstractSignListener.SignWrapper sign, REFRESH_CAUSE refreshCause) { + if (refreshCause != REFRESH_CAUSE.SIGN_MOVED_BY_CRAFT && sign.block() != null) { + sign.block().update(); + } + } - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) - return; + @Override + protected void onCraftIsBusy(Player player, Craft craft) { - event.setCancelled(true); - event.getPlayer().performCommand("contacts"); } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java index d8a488576..aeebd3f3e 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java @@ -1,5 +1,13 @@ package net.countercraft.movecraft.features.status; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.craft.type.CraftType; +import net.countercraft.movecraft.craft.type.RequiredBlockEntry; +import net.countercraft.movecraft.sign.AbstractInformationSign; +import net.countercraft.movecraft.sign.AbstractSignListener; import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.type.CraftType; @@ -8,53 +16,78 @@ import net.countercraft.movecraft.events.SignTranslateEvent; import net.countercraft.movecraft.util.Counter; import net.countercraft.movecraft.util.Tags; -import org.bukkit.ChatColor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; import org.bukkit.Material; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public final class StatusSign implements Listener { +import java.util.List; - @EventHandler - public void onCraftDetect(CraftDetectEvent event) { - World world = event.getCraft().getWorld(); - for (MovecraftLocation location : event.getCraft().getHitBox()) { - var block = location.toBukkit(world).getBlock(); - if (!Tag.SIGNS.isTagged(block.getType())) { - continue; - } - BlockState state = block.getState(); - if (state instanceof Sign) { - Sign sign = (Sign) state; - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Status:")) { - sign.setLine(1, ""); - sign.setLine(2, ""); - sign.setLine(3, ""); - sign.update(); - } - } +// TODO: Split this into multiple signs? Separate sign for fuel would make sense +public class StatusSign extends AbstractInformationSign { + + Object2IntMap displayBlocks = new Object2IntOpenHashMap<>(); + List displayComponents = new ObjectArrayList<>(); + int totalNonNegligibleBlocks = 0; + int totalNonNegligibleWaterBlocks = 0; + + protected static final int FUEL_LINE_INDEX = 3; + protected static final int BLOCK_LINE_INDEX_TOP = 1; + protected static final int BLOCK_LINE_INDEX_BOTTOM = 2; + + @Override + protected @Nullable Component getUpdateString(int lineIndex, Component oldData, Craft craft) { + switch(lineIndex) { + case FUEL_LINE_INDEX: + return calcFuel(craft); + case BLOCK_LINE_INDEX_TOP: + return displayComponents.get(0); + case BLOCK_LINE_INDEX_BOTTOM: + return displayComponents.get(1); } + return oldData; } - @EventHandler - public final void onSignTranslate(SignTranslateEvent event) { - Craft craft = event.getCraft(); - if (!ChatColor.stripColor(event.getLine(0)).equalsIgnoreCase("Status:")) { - return; - } + protected Component calcFuel(Craft craft) { double fuel = craft.getDataTag(Craft.FUEL); + int cruiseSkipBlocks = (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_CRUISE_SKIP_BLOCKS, craft.getWorld()); + cruiseSkipBlocks++; + double fuelBurnRate = (double) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_FUEL_BURN_RATE, craft.getWorld()); + int fuelRange = (int) Math.round((fuel * (1 + cruiseSkipBlocks)) / fuelBurnRate); + // DONE: Create constants in base class for style colors! + Style style; + if (fuelRange > 1000) { + style = STYLE_COLOR_GREEN; + } else if (fuelRange > 100) { + style = STYLE_COLOR_YELLOW; + } else { + style = STYLE_COLOR_RED; + } + + return Component.text("Fuel range: " + fuelRange).style(style); + } - int totalNonNegligibleBlocks = 0; - int totalNonNegligibleWaterBlocks = 0; + @Override + protected @Nullable Component getDefaultString(int lineIndex, Component oldComponent) { + return EMPTY; + } + + @Override + protected boolean refreshSign(@Nullable Craft craft, AbstractSignListener.SignWrapper sign, boolean fillDefault, REFRESH_CAUSE refreshCause) { + // Calculate blocks and store them temporary, not pretty but works! + calcDisplayBlocks(craft); + calcdisplayComponents(craft); + return super.refreshSign(craft, sign, fillDefault, refreshCause); + } + + protected void calcDisplayBlocks(Craft craft) { + displayBlocks.clear(); + displayComponents.clear(); + + totalNonNegligibleBlocks = 0; + totalNonNegligibleWaterBlocks = 0; Counter materials = craft.getDataTag(Craft.MATERIALS); if (materials.isEmpty()) { return; @@ -70,13 +103,32 @@ public final void onSignTranslate(SignTranslateEvent event) { } } - Counter displayBlocks = new Counter<>(); - displayBlocks.add(craft.getDataTag(Craft.FLYBLOCKS)); - displayBlocks.add(craft.getDataTag(Craft.MOVEBLOCKS)); + Counter displayBlocksTmp = new Counter<>(); + displayBlocksTmp.add(craft.getDataTag(Craft.FLYBLOCKS)); + displayBlocksTmp.add(craft.getDataTag(Craft.MOVEBLOCKS)); + + for (RequiredBlockEntry entry : displayBlocksTmp.getKeySet()) { + // TODO: Sure? + if (entry.getMin() == 0.0) { + continue; + } + double pctPresent = (displayBlocksTmp.get(entry) * 100D); + // TODO: WTF? Why? + if (craft.getType().getBoolProperty(CraftType.BLOCKED_BY_WATER)) { + pctPresent /= totalNonNegligibleBlocks; + } else { + pctPresent /= totalNonNegligibleWaterBlocks; + } + displayBlocks.putIfAbsent(entry, (int) pctPresent); + } + } - int signLine = 1; + protected void calcdisplayComponents(Craft craft) { + displayComponents.set(0, EMPTY); + displayComponents.set(1, EMPTY); + int signLine = 0; int signColumn = 0; - for (RequiredBlockEntry entry : displayBlocks.getKeySet()) { + for (RequiredBlockEntry entry : displayBlocks.keySet()) { if (entry.getMin() == 0.0) { continue; } @@ -86,67 +138,45 @@ public final void onSignTranslate(SignTranslateEvent event) { } else { percentPresent /= totalNonNegligibleWaterBlocks; } - String signText = ""; + Component signText = EMPTY; + Style style; if (percentPresent > entry.getMin() * 1.04) { - signText += ChatColor.GREEN; + style = STYLE_COLOR_GREEN; } else if (percentPresent > entry.getMin() * 1.02) { - signText += ChatColor.YELLOW; + style = STYLE_COLOR_YELLOW; } else { - signText += ChatColor.RED; + style = STYLE_COLOR_RED; } if (entry.getName() == null) { - signText += entry.materialsToString().toUpperCase().charAt(0); + signText = Component.text(entry.materialsToString().toUpperCase().charAt(0)); } else { - signText += entry.getName().toUpperCase().charAt(0); + signText = Component.text(entry.getName().toUpperCase().charAt(0)); } - signText += " "; - signText += (int) percentPresent; - signText += "/"; - signText += (int) entry.getMin(); - signText += " "; + signText = signText.append(Component.text(" " + (int)percentPresent + "/" + (int)entry.getMin() + " ")); + signText = signText.style(style); if (signColumn == 0) { - event.setLine(signLine, signText); + displayComponents.set(signLine, signText); signColumn++; - } else if (signLine < 3) { - String existingLine = event.getLine(signLine); - existingLine += signText; - event.setLine(signLine, existingLine); + } else if (signLine < 2) { + Component existingLine = displayComponents.get(signLine); + existingLine = existingLine.append(signText); + displayComponents.set(signLine, existingLine); signLine++; signColumn = 0; } } + } - String fuelText = ""; - int cruiseSkipBlocks = (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_CRUISE_SKIP_BLOCKS, craft.getWorld()); - cruiseSkipBlocks++; - double fuelBurnRate = (double) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_FUEL_BURN_RATE, craft.getWorld()); - int fuelRange = (int) Math.round((fuel * (1 + cruiseSkipBlocks)) / fuelBurnRate); - if (fuelRange > 1000) { - fuelText += ChatColor.GREEN; - } else if (fuelRange > 100) { - fuelText += ChatColor.YELLOW; - } else { - fuelText += ChatColor.RED; + @Override + protected void performUpdate(Component[] newComponents, AbstractSignListener.SignWrapper sign, REFRESH_CAUSE refreshCause) { + if (refreshCause != REFRESH_CAUSE.SIGN_MOVED_BY_CRAFT && sign.block() != null) { + sign.block().update(true); } - fuelText += "Fuel range:"; - fuelText += fuelRange; - event.setLine(signLine, fuelText); } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClickEvent(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - Block block = event.getClickedBlock(); - if (!(block.getState() instanceof Sign)) { - return; - } + @Override + protected void onCraftIsBusy(Player player, Craft craft) { - Sign sign = (Sign) block.getState(); - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Status:")) { - return; - } - event.setCancelled(true); } -} \ No newline at end of file + +} diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/listener/CraftTypeListener.java b/Movecraft/src/main/java/net/countercraft/movecraft/listener/CraftTypeListener.java new file mode 100644 index 000000000..6b3748b89 --- /dev/null +++ b/Movecraft/src/main/java/net/countercraft/movecraft/listener/CraftTypeListener.java @@ -0,0 +1,17 @@ +package net.countercraft.movecraft.listener; + +import net.countercraft.movecraft.craft.CraftManager; +import net.countercraft.movecraft.events.TypesReloadedEvent; +import net.countercraft.movecraft.sign.AbstractMovecraftSign; +import net.countercraft.movecraft.sign.CraftPilotSign; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class CraftTypeListener implements Listener { + + @EventHandler + public void onReload(TypesReloadedEvent event) { + AbstractMovecraftSign.registerCraftPilotSigns(CraftManager.getInstance().getCraftTypes(), CraftPilotSign::new); + } + +} diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftRotateCommand.java b/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftRotateCommand.java index a6190e5a0..19c5f84de 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftRotateCommand.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftRotateCommand.java @@ -1,8 +1,5 @@ package net.countercraft.movecraft.mapUpdater.update; -import it.unimi.dsi.fastutil.Hash; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import net.countercraft.movecraft.Movecraft; import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.MovecraftRotation; @@ -13,35 +10,18 @@ import net.countercraft.movecraft.craft.SinkingCraft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.events.CraftReleaseEvent; -import net.countercraft.movecraft.events.SignTranslateEvent; import net.countercraft.movecraft.util.CollectionUtils; import net.countercraft.movecraft.util.MathUtils; import net.countercraft.movecraft.util.Tags; import net.countercraft.movecraft.util.hitboxes.HitBox; import net.countercraft.movecraft.util.hitboxes.SetHitBox; import net.countercraft.movecraft.util.hitboxes.SolidHitBox; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Tag; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.block.data.BlockData; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; +import java.util.*; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -202,7 +182,8 @@ public void doUpdate() { } private void sendSignEvents() { - Object2ObjectMap> signs = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { + Movecraft.getInstance().getAbstractSignListener().processSignTranslation(craft, true); + /* Object2ObjectMap> signs = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { @Override public int hashCode(String[] strings) { return Arrays.hashCode(strings); @@ -218,6 +199,7 @@ public boolean equals(String[] a, String[] b) { for (MovecraftLocation location : craft.getHitBox()) { Block block = location.toBukkit(craft.getWorld()).getBlock(); BlockState state = block.getState(); + // TODO: Change to return the signwrappers for this sign (if not empty!) if (state instanceof Sign) { Sign sign = (Sign) block.getState(); if (!signs.containsKey(sign.getLines())) @@ -250,7 +232,7 @@ public boolean equals(String[] a, String[] b) { sign.update(false, false); block.setBlockData(data); } - } + }*/ } @NotNull diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftTranslateCommand.java b/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftTranslateCommand.java index 7fa64e43a..7a4d8b057 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftTranslateCommand.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/CraftTranslateCommand.java @@ -2,9 +2,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import it.unimi.dsi.fastutil.Hash; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import net.countercraft.movecraft.Movecraft; import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.WorldHandler; @@ -14,37 +11,19 @@ import net.countercraft.movecraft.craft.SinkingCraft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.events.CraftReleaseEvent; -import net.countercraft.movecraft.events.SignTranslateEvent; import net.countercraft.movecraft.util.MathUtils; import net.countercraft.movecraft.util.Tags; import net.countercraft.movecraft.util.hitboxes.HitBox; import net.countercraft.movecraft.util.hitboxes.SetHitBox; import net.countercraft.movecraft.util.hitboxes.SolidHitBox; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Tag; import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; import org.bukkit.block.data.Waterlogged; import org.jetbrains.annotations.NotNull; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; +import java.util.*; import java.util.logging.Logger; public class CraftTranslateCommand extends UpdateCommand { @@ -303,7 +282,8 @@ private LinkedList hullSearch(SetHitBox validExterior) { } private void sendSignEvents(){ - Object2ObjectMap> signs = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { + Movecraft.getInstance().getAbstractSignListener().processSignTranslation(craft, false); + /*Object2ObjectMap> signs = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { @Override public int hashCode(String[] strings) { return Arrays.hashCode(strings); @@ -345,12 +325,12 @@ public boolean equals(String[] a, String[] b) { continue; } Sign sign = signStates.get(location); - for(int i = 0; i<4; i++){ + for(int i = 0; i<4; i++) { sign.setLine(i, entry.getKey()[i]); } sign.update(false, false); } - } + }*/ } @NotNull diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/validators/PilotSignValidator.java b/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/validators/PilotSignValidator.java index 045b9d47d..c429a3e5a 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/validators/PilotSignValidator.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/validators/PilotSignValidator.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +// TODO: Remove, replaced by the sign implementation! public class PilotSignValidator implements DetectionPredicate { @Override @Contract(pure = true) diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/AscendSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/AscendSign.java index e59193e3b..60dc0b1e3 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/AscendSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/AscendSign.java @@ -1,93 +1,52 @@ package net.countercraft.movecraft.sign; import net.countercraft.movecraft.CruiseDirection; -import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.craft.type.CraftType; -import net.countercraft.movecraft.events.CraftDetectEvent; -import org.bukkit.ChatColor; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; -public class AscendSign implements Listener { +public class AscendSign extends AbstractCruiseSign { - @EventHandler - public void onCraftDetect(CraftDetectEvent event){ - World world = event.getCraft().getWorld(); - for(MovecraftLocation location: event.getCraft().getHitBox()){ - var block = location.toBukkit(world).getBlock(); - if(!Tag.SIGNS.isTagged(block.getType())){ - continue; - } - BlockState state = block.getState(); - if(block.getState() instanceof Sign){ - Sign sign = (Sign) block.getState(); - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Ascend: ON")) { - sign.setLine(0, "Ascend: OFF"); - sign.update(); - } - } - } + public AscendSign() { + super(true, "ON", "OFF"); } + @Override + protected void setCraftCruising(Player player, CruiseDirection direction, Craft craft) { + craft.setCruiseDirection(direction); + craft.setLastCruiseUpdate(System.currentTimeMillis()); + craft.setCruising(true); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClickEvent(@NotNull PlayerInteractEvent event){ - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - Block block = event.getClickedBlock(); - if (!(block.getState() instanceof Sign)){ - return; - } + @Override + protected CruiseDirection getCruiseDirection(AbstractSignListener.SignWrapper sign) { + return CruiseDirection.UP; + } - Sign sign = (Sign) block.getState(); - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Ascend: OFF")) { - event.setCancelled(true); - if (CraftManager.getInstance().getCraftByPlayer(event.getPlayer()) == null) { - return; - } - Craft c = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (!c.getType().getBoolProperty(CraftType.CAN_CRUISE)) { - return; - } - //c.resetSigns(true, false, true); - sign.setLine(0, "Ascend: ON"); - sign.update(true); + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + // Ignore + } - c.setCruiseDirection(CruiseDirection.UP); - c.setLastCruiseUpdate(System.currentTimeMillis()); - c.setCruising(true); - c.resetSigns(sign); + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { - if (!c.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { - CraftManager.getInstance().addReleaseTask(c); - } - return; - } - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Ascend: ON")) { - return; - } - event.setCancelled(true); - Craft c = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (c == null || !c.getType().getBoolProperty(CraftType.CAN_CRUISE)) { - return; - } - sign.setLine(0, "Ascend: OFF"); - sign.update(true); + } - c.setCruising(false); - c.resetSigns(sign); + @Override + protected void onAfterStoppingCruise(Craft craft, AbstractSignListener.SignWrapper signWrapper, Player player) { + if (!craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { + CraftManager.getInstance().addReleaseTask(craft); + } + } + @Override + protected boolean canPlayerUseSignOn(Player player, @Nullable Craft craft) { + if (super.canPlayerUseSignOn(player, craft)) { + return craft.getType().getBoolProperty(CraftType.CAN_CRUISE); + } + return false; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/CraftSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/CraftPilotSign.java similarity index 60% rename from Movecraft/src/main/java/net/countercraft/movecraft/sign/CraftSign.java rename to Movecraft/src/main/java/net/countercraft/movecraft/sign/CraftPilotSign.java index ec5a977c5..a3827e8b0 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/CraftSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/CraftPilotSign.java @@ -4,12 +4,7 @@ import net.countercraft.movecraft.Movecraft; import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.config.Settings; -import net.countercraft.movecraft.craft.Craft; -import net.countercraft.movecraft.craft.CraftManager; -import net.countercraft.movecraft.craft.CruiseOnPilotCraft; -import net.countercraft.movecraft.craft.CruiseOnPilotSubCraft; -import net.countercraft.movecraft.craft.PlayerCraftImpl; -import net.countercraft.movecraft.craft.SubCraft; +import net.countercraft.movecraft.craft.*; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.events.CraftPilotEvent; import net.countercraft.movecraft.events.CraftReleaseEvent; @@ -17,76 +12,77 @@ import net.countercraft.movecraft.processing.functions.Result; import net.countercraft.movecraft.util.Pair; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.block.data.Directional; +import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.block.SignChangeEvent; -import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.scheduler.BukkitRunnable; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Collections; import java.util.HashSet; +import java.util.Optional; import java.util.Set; -public final class CraftSign implements Listener { - private final Set piloting = new HashSet<>(); +//TODO: This is not very pretty... +public class CraftPilotSign extends AbstractCraftPilotSign { - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignChange(@NotNull SignChangeEvent event) { - if (CraftManager.getInstance().getCraftTypeFromString(event.getLine(0)) == null) - return; + static final Set PILOTING = Collections.synchronizedSet(new HashSet<>()); - if (!Settings.RequireCreatePerm) - return; - - if (!event.getPlayer().hasPermission("movecraft." + ChatColor.stripColor(event.getLine(0)) + ".create")) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); - event.setCancelled(true); - } + public CraftPilotSign(CraftType craftType) { + super(craftType); } - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) - return; - - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) - return; - - Sign sign = (Sign) state; - CraftType craftType = CraftManager.getInstance().getCraftTypeFromString(ChatColor.stripColor(sign.getLine(0))); - if (craftType == null) - return; + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + return processingSuccessful || !sneaking; + } - // Valid sign prompt for ship command. - event.setCancelled(true); - Player player = event.getPlayer(); - if (!player.hasPermission("movecraft." + ChatColor.stripColor(sign.getLine(0)) + ".pilot")) { + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + String header = sign.getRaw(0).trim(); + CraftType craftType = CraftManager.getInstance().getCraftTypeFromString(header); + if (craftType != this.craftType) { + return false; + } + if (!player.hasPermission("movecraft." + header + ".pilot")) { player.sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); - return; + return false; + } else { + return true; } + } - Location loc = event.getClickedBlock().getLocation(); + @Override + protected boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, @javax.annotation.Nullable Craft craft) { + if (this.craftType.getBoolProperty(CraftType.MUST_BE_SUBCRAFT) && craft == null) { + return false; + } + World world = sign.block().getWorld(); + if (craft != null) { + world = craft.getWorld(); + } + Location loc = sign.block().getLocation(); MovecraftLocation startPoint = new MovecraftLocation(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); - if (piloting.contains(startPoint)) { - return; + + if (PILOTING.contains(startPoint)) { + // Always return true + return true; } - // Attempt to run detection - World world = event.getClickedBlock().getWorld(); + runDetectTask(startPoint, player, sign, craft, world); + + return true; + } + protected void runDetectTask(MovecraftLocation startPoint, Player player, AbstractSignListener.SignWrapper signWrapper, Craft parentCraft, World world) { + PILOTING.add(startPoint); CraftManager.getInstance().detect( startPoint, craftType, (type, w, p, parents) -> { + // Assert instructions are not available normally, also this is checked in beforehand sort of assert p != null; // Note: This only passes in a non-null player. if (type.getBoolProperty(CraftType.CRUISE_ON_PILOT)) { if (parents.size() > 1) @@ -122,10 +118,12 @@ public void onSignClick(@NotNull PlayerInteractEvent event) { if (craft.getType().getBoolProperty(CraftType.CRUISE_ON_PILOT)) { // Setup cruise direction - if (sign.getBlockData() instanceof Directional) + BlockFace facing = signWrapper.facing(); + craft.setCruiseDirection(CruiseDirection.fromBlockFace(facing)); + /*if (signWrapper.block().getBlockData() instanceof Directional) craft.setCruiseDirection(CruiseDirection.fromBlockFace(((Directional) sign.getBlockData()).getFacing())); else - craft.setCruiseDirection(CruiseDirection.NONE); + craft.setCruiseDirection(CruiseDirection.NONE);*/ // Start craft cruising craft.setLastCruiseUpdate(System.currentTimeMillis()); @@ -148,11 +146,34 @@ public void run() { } } ); + // TODO: Move this to be directly called by the craftmanager post detection... + // Or use the event handler or something new BukkitRunnable() { @Override public void run() { - piloting.remove(startPoint); + PILOTING.remove(startPoint); } }.runTaskLater(Movecraft.getInstance(), 4); + + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + String header = sign.getRaw(0).trim(); + CraftType craftType = CraftManager.getInstance().getCraftTypeFromString(header); + if (craftType != this.craftType) { + return false; + } + if (Settings.RequireCreatePerm) { + Player player = event.getPlayer(); + if (!player.hasPermission("movecraft." + header + ".create")) { + player.sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); + return false; + } else { + return true; + } + } else { + return true; + } } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/CruiseSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/CruiseSign.java index fd4efec28..5c28d2cb1 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/CruiseSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/CruiseSign.java @@ -1,104 +1,73 @@ package net.countercraft.movecraft.sign; import net.countercraft.movecraft.CruiseDirection; -import net.countercraft.movecraft.MovecraftLocation; -import net.countercraft.movecraft.config.Settings; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.craft.type.CraftType; -import net.countercraft.movecraft.events.CraftDetectEvent; -import net.countercraft.movecraft.localisation.I18nSupport; -import org.bukkit.ChatColor; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.block.data.Directional; +import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.block.Action; -import org.bukkit.event.block.SignChangeEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public final class CruiseSign implements Listener { +public class CruiseSign extends AbstractCruiseSign { - @EventHandler - public void onCraftDetect(@NotNull CraftDetectEvent event) { - World world = event.getCraft().getWorld(); - for (MovecraftLocation location : event.getCraft().getHitBox()) { - var block = location.toBukkit(world).getBlock(); - if (!Tag.SIGNS.isTagged(block.getType())) - continue; - - BlockState state = block.getState(); - if (!(state instanceof Sign)) - continue; - Sign sign = (Sign) state; - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Cruise: ON")) { - sign.setLine(0, "Cruise: OFF"); - sign.update(); - } - } + public CruiseSign() { + super("movecraft.cruisesign", true, "ON", "OFF"); } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) - return; - - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) - return; - - Sign sign = (Sign) state; - String line = ChatColor.stripColor(sign.getLine(0)); - if (line.equalsIgnoreCase("Cruise: OFF")) { - event.setCancelled(true); - Craft c = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (c == null || !c.getType().getBoolProperty(CraftType.CAN_CRUISE)) - return; - if (!(sign.getBlockData() instanceof Directional)) - return; + @Override + protected void setCraftCruising(Player player, CruiseDirection direction, Craft craft) { + craft.setCruiseDirection(direction); + craft.setLastCruiseUpdate(System.currentTimeMillis()); + craft.setCruising(true); + } - sign.setLine(0, "Cruise: ON"); - sign.update(true); + @Override + protected CruiseDirection getCruiseDirection(AbstractSignListener.SignWrapper sign) { + BlockFace face = sign.facing(); + face = face.getOppositeFace(); + return CruiseDirection.fromBlockFace(face); + } - c.setCruiseDirection(CruiseDirection.fromBlockFace(((Directional) sign.getBlockData()).getFacing())); - c.setLastCruiseUpdate(System.currentTimeMillis()); - c.setCruising(true); - c.resetSigns(sign); - if (!c.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { - CraftManager.getInstance().addReleaseTask(c); + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (super.isSignValid(clickType, sign, player)) { + switch(sign.facing()) { + case NORTH: + case EAST: + case SOUTH: + case WEST: + return true; + default: + return false; } } - else if (line.equalsIgnoreCase("Cruise: ON")) { - event.setCancelled(true); - Craft c = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (c == null || !c.getType().getBoolProperty(CraftType.CAN_CRUISE)) - return; + return false; + } - sign.setLine(0, "Cruise: OFF"); - sign.update(true); - c.setCruising(false); - c.resetSigns(sign); + @Override + protected void onAfterStoppingCruise(Craft craft, AbstractSignListener.SignWrapper signWrapper, Player player) { + super.onAfterStoppingCruise(craft, signWrapper, player); + if (!craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { + CraftManager.getInstance().addReleaseTask(craft); } } - @EventHandler(priority = EventPriority.NORMAL) - public void onSignChange(@NotNull SignChangeEvent event) { - Player player = event.getPlayer(); - String line = ChatColor.stripColor(event.getLine(0)); - if (line == null) - return; - if (!line.equalsIgnoreCase("Cruise: OFF") && !line.equalsIgnoreCase("Cruise: ON")) - return; - if (player.hasPermission("movecraft.cruisesign") || !Settings.RequireCreatePerm) - return; + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + // Ignore + } + + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { - player.sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); - event.setCancelled(true); + } + + @Override + protected boolean canPlayerUseSignOn(Player player, @Nullable Craft craft) { + if (super.canPlayerUseSignOn(player, craft)) { + return craft.getType().getBoolProperty(CraftType.CAN_CRUISE); + } + return false; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/DescendSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/DescendSign.java index 2d638689b..2ccecd465 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/DescendSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/DescendSign.java @@ -1,86 +1,53 @@ package net.countercraft.movecraft.sign; import net.countercraft.movecraft.CruiseDirection; -import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.craft.type.CraftType; -import net.countercraft.movecraft.events.CraftDetectEvent; -import org.bukkit.ChatColor; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; -public final class DescendSign implements Listener{ +// TODO: Unify with AscendSign to use a common "VerticalCruiseSign" class +public class DescendSign extends AbstractCruiseSign { - @EventHandler - public void onCraftDetect(CraftDetectEvent event){ - World world = event.getCraft().getWorld(); - for(MovecraftLocation location: event.getCraft().getHitBox()){ - var block = location.toBukkit(world).getBlock(); - if(!Tag.SIGNS.isTagged(block.getType())){ - continue; - } - BlockState state = block.getState(); - if(state instanceof Sign){ - Sign sign = (Sign) state; - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Descend: ON")) { - sign.setLine(0, "Descend: OFF"); - sign.update(); - } - } - } + public DescendSign() { + super(true, "ON", "OFF"); } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) { - return; - } - Sign sign = (Sign) state; - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Descend: OFF")) { - event.setCancelled(true); - if (CraftManager.getInstance().getCraftByPlayer(event.getPlayer()) == null) { - return; - } - Craft c = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (!c.getType().getBoolProperty(CraftType.CAN_CRUISE)) { - return; - } - //c.resetSigns(true, true, false); - sign.setLine(0, "Descend: ON"); - sign.update(true); + @Override + protected void setCraftCruising(Player player, CruiseDirection direction, Craft craft) { + craft.setCruiseDirection(direction); + craft.setLastCruiseUpdate(System.currentTimeMillis()); + craft.setCruising(true); + } + + @Override + protected CruiseDirection getCruiseDirection(AbstractSignListener.SignWrapper sign) { + return CruiseDirection.DOWN; + } - c.setCruiseDirection(CruiseDirection.DOWN); - c.setLastCruiseUpdate(System.currentTimeMillis()); - c.setCruising(true); - c.resetSigns(sign); + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + // Ignore + } + + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + + } - if (!c.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { - CraftManager.getInstance().addReleaseTask(c); - } - return; + @Override + protected void onAfterStoppingCruise(Craft craft, AbstractSignListener.SignWrapper signWrapper, Player player) { + if (!craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { + CraftManager.getInstance().addReleaseTask(craft); } - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Descend: ON")) { - event.setCancelled(true); - Craft c = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (c != null && c.getType().getBoolProperty(CraftType.CAN_CRUISE)) { - sign.setLine(0, "Descend: OFF"); - sign.update(true); - c.setCruising(false); - c.resetSigns(sign); - } + } + + @Override + protected boolean canPlayerUseSignOn(Player player, @Nullable Craft craft) { + if (super.canPlayerUseSignOn(player, craft)) { + return craft.getType().getBoolProperty(CraftType.CAN_CRUISE); } + return false; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/HelmSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/HelmSign.java index 74eef63fa..1c5afc392 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/HelmSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/HelmSign.java @@ -2,62 +2,91 @@ import net.countercraft.movecraft.MovecraftRotation; import net.countercraft.movecraft.craft.Craft; -import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.localisation.I18nSupport; import net.countercraft.movecraft.util.MathUtils; import org.bukkit.ChatColor; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public final class HelmSign implements Listener { +public class HelmSign extends AbstractCraftSign { + + public static final String[] PRETTY_LINES = new String[] { + "\\ || /", + "\\ || /", + "/ || \\" + }; + public static final String PRETTY_HEADER = PRETTY_LINES[0]; + + public HelmSign() { + super(false); + } @EventHandler public void onSignChange(SignChangeEvent event){ if (!ChatColor.stripColor(event.getLine(0)).equalsIgnoreCase("[helm]")) { return; } + for (int i = 0; i < PRETTY_LINES.length && i < event.getLines().length; i++) { + event.setLine(i, PRETTY_LINES[i]); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onSignClick(@NotNull PlayerInteractEvent event) { + + + } + + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + + } + + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + + } + + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + return !sneaking; + } + + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + // Nothing to check here honestly... + return true; + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + if (!ChatColor.stripColor(event.getLine(0)).equalsIgnoreCase("[helm]")) { + return true; + } event.setLine(0, "\\ || /"); event.setLine(1, "== =="); event.setLine(2, "/ || \\"); + return true; } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { MovecraftRotation rotation; - if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { + if (clickType == Action.RIGHT_CLICK_BLOCK) { rotation = MovecraftRotation.CLOCKWISE; - }else if(event.getAction() == Action.LEFT_CLICK_BLOCK){ + }else if(clickType == Action.LEFT_CLICK_BLOCK){ rotation = MovecraftRotation.ANTICLOCKWISE; }else{ - return; - } - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) { - return; - } - Sign sign = (Sign) state; - if (!(ChatColor.stripColor(sign.getLine(0)).equals("\\ || /") && - ChatColor.stripColor(sign.getLine(1)).equals("== ==") && - ChatColor.stripColor(sign.getLine(2)).equals("/ || \\"))) { - return; - } - event.setCancelled(true); - Craft craft = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (craft == null) { - return; - } - if (!event.getPlayer().hasPermission("movecraft." + craft.getType().getStringProperty(CraftType.NAME) + ".rotate")) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); - return; + return false; } + /*Long time = timeMap.get(event.getPlayer()); if (time != null) { long ticksElapsed = (System.currentTimeMillis() - time) / 50; @@ -74,13 +103,14 @@ public void onSignClick(@NotNull PlayerInteractEvent event) { } }*/ - if(!MathUtils.locIsNearCraftFast(craft, MathUtils.bukkit2MovecraftLoc(event.getPlayer().getLocation()))) - return; + if(!MathUtils.locIsNearCraftFast(craft, MathUtils.bukkit2MovecraftLoc(player.getLocation()))) + return false; + // TODO: Why was this used before? CraftManager.getInstance().getCraftByPlayer(event.getPlayer())... The craft variable did exist, so why don't use it? if (craft.getType().getBoolProperty(CraftType.ROTATE_AT_MIDPOINT)) { - CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).rotate(rotation, craft.getHitBox().getMidPoint()); + craft.rotate(rotation, craft.getHitBox().getMidPoint()); } else { - CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).rotate(rotation, MathUtils.bukkit2MovecraftLoc(sign.getLocation())); + craft.rotate(rotation, MathUtils.bukkit2MovecraftLoc(sign.block().getLocation())); } //timeMap.put(event.getPlayer(), System.currentTimeMillis()); @@ -93,5 +123,18 @@ public void onSignClick(@NotNull PlayerInteractEvent event) { curTickCooldown = curTickCooldown * 2;*/ //CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).setCurTickCooldown(curTickCooldown); // lose half your speed when turning + return false; + } + + @Override + protected boolean canPlayerUseSignOn(Player player, Craft craft) { + if (super.canPlayerUseSignOn(player, craft)) { + if (!player.hasPermission("movecraft." + craft.getType().getStringProperty(CraftType.NAME) + ".rotate")) { + player.sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); + return false; + } + return true; + } + return false; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/MoveSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/MoveSign.java index 37c0f00d4..9a65437b2 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/MoveSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/MoveSign.java @@ -1,80 +1,105 @@ package net.countercraft.movecraft.sign; -import net.countercraft.movecraft.craft.CraftManager; +import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.localisation.I18nSupport; -import org.bukkit.ChatColor; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.bukkit.event.block.SignChangeEvent; +import org.jetbrains.annotations.Nullable; -public final class MoveSign implements Listener{ - private static final String HEADER = "Move:"; +public class MoveSign extends AbstractCraftSign { - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; + public MoveSign() { + super(null, false); + } + + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + player.sendMessage(I18nSupport.getInternationalisedString("Detection - Parent Craft is busy")); + } + + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + + } + + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + if (processingSuccessful) { + return true; } - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) { - return; + return !sneaking; + } + + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + String[] numbers = sign.getRaw(1).split(","); + if (numbers.length != 3) { + return false; } - Sign sign = (Sign) state; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; + for (String s : numbers) { + try { + Integer.parseInt(s); + } catch(NumberFormatException nfe) { + return false; + } } - event.setCancelled(true); - if (CraftManager.getInstance().getCraftByPlayer(event.getPlayer()) == null) { - return; + return true; + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + return false; + } + + @Override + protected boolean canPlayerUseSignOn(Player player, Craft craft) { + if (!super.canPlayerUseSignOn(player, craft)) { + return false; } - /*Long time = timeMap.get(event.getPlayer()); - if (time != null) { - long ticksElapsed = (System.currentTimeMillis() - time) / 50; - Craft craft = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - // if the craft should go slower underwater, make time pass - // more slowly there - if (craft.getType().getHalfSpeedUnderwater() && craft.getMinY() < craft.getW().getSeaLevel()) { - ticksElapsed = ticksElapsed >> 1; - } - if (Math.abs(ticksElapsed) < CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getTickCooldown()) { - event.setCancelled(true); - return; - } - }*/ - String[] numbers = ChatColor.stripColor(sign.getLine(1)).split(","); - int dx = Integer.parseInt(numbers[0]); - int dy = Integer.parseInt(numbers[1]); - int dz = Integer.parseInt(numbers[2]); - int maxMove = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getIntProperty(CraftType.MAX_STATIC_MOVE); - - if (dx > maxMove) - dx = maxMove; - if (dx < 0 - maxMove) - dx = 0 - maxMove; - if (dy > maxMove) - dy = maxMove; - if (dy < 0 - maxMove) - dy = 0 - maxMove; - if (dz > maxMove) - dz = maxMove; - if (dz < 0 - maxMove) - dz = 0 - maxMove; - - if (!event.getPlayer().hasPermission("movecraft." + CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getStringProperty(CraftType.NAME) + ".move")) { - event.getPlayer().sendMessage( + if (!player.hasPermission("movecraft." + craft.getType().getStringProperty(CraftType.NAME) + ".move")) { + player.sendMessage( I18nSupport.getInternationalisedString("Insufficient Permissions")); - return; + return false; } - if (CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getBoolProperty(CraftType.CAN_STATIC_MOVE)) { - CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).translate(dx, dy, dz); - //timeMap.put(event.getPlayer(), System.currentTimeMillis()); - CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).setLastCruiseUpdate(System.currentTimeMillis()); + return true; + } + + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { + if (!craft.getType().getBoolProperty(CraftType.CAN_STATIC_MOVE)) { + return false; } + + String[] numbers = sign.getRaw(1).split(","); + int dx = Integer.parseInt(numbers[0]); + int dy = Integer.parseInt(numbers[1]); + int dz = Integer.parseInt(numbers[2]); + + return translateCraft(sign.block().getRawData(), dx, dy, dz, craft, sign); + } + + protected boolean translateCraft(final byte signDataRaw, int dxRaw, int dyRaw, int dzRaw, Craft craft, AbstractSignListener.SignWrapper signWrapper) { + int maxMove = craft.getType().getIntProperty(CraftType.MAX_STATIC_MOVE); + + if (dxRaw > maxMove) + dxRaw = maxMove; + if (dxRaw < 0 - maxMove) + dxRaw = 0 - maxMove; + if (dyRaw > maxMove) + dyRaw = maxMove; + if (dyRaw < 0 - maxMove) + dyRaw = 0 - maxMove; + if (dzRaw > maxMove) + dzRaw = maxMove; + if (dzRaw < 0 - maxMove) + dzRaw = 0 - maxMove; + + craft.translate(dxRaw, dyRaw, dzRaw); + //timeMap.put(event.getPlayer(), System.currentTimeMillis()); + craft.setLastCruiseUpdate(System.currentTimeMillis()); + + return true; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/NameSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/NameSign.java index 787f3711c..159fdf45d 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/NameSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/NameSign.java @@ -1,84 +1,83 @@ package net.countercraft.movecraft.sign; -import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.config.Settings; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.PilotedCraft; import net.countercraft.movecraft.events.CraftDetectEvent; import net.countercraft.movecraft.util.ChatUtils; -import org.bukkit.ChatColor; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; import org.bukkit.event.block.SignChangeEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; import java.util.Arrays; import java.util.stream.Collectors; -public final class NameSign implements Listener { - private static final String HEADER = "Name:"; - @EventHandler - public void onCraftDetect(@NotNull CraftDetectEvent event) { - Craft c = event.getCraft(); +public class NameSign extends AbstractCraftSign { - if (c instanceof PilotedCraft) { - PilotedCraft pilotedCraft = (PilotedCraft) c; - if (Settings.RequireNamePerm && !pilotedCraft.getPilot().hasPermission("movecraft.name.place")) - return; - } + public static final String NAME_SIGN_PERMISSION = "movecraft.name.place"; - World w = c.getWorld(); + public NameSign() { + super(NAME_SIGN_PERMISSION, true); + } - for (MovecraftLocation location : c.getHitBox()) { - var block = location.toBukkit(w).getBlock(); - if(!Tag.SIGNS.isTagged(block.getType())){ - continue; - } - BlockState state = block.getState(); - if (!(state instanceof Sign)) { - return; - } - Sign sign = (Sign) state; - if (sign.getLine(0).equalsIgnoreCase(HEADER)) { - String name = Arrays.stream(sign.getLines()).skip(1).filter(f -> f != null - && !f.trim().isEmpty()).collect(Collectors.joining(" ")); - c.setName(name); - return; - } + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + if (type == null) { + return !processingSuccessful; } + return !sneaking; + } + + @Override + protected boolean canPlayerUseSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + return !Settings.RequireNamePerm || super.canPlayerUseSign(clickType, sign, player); + } + + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + return true; + } + + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { + return true; + } + + @Override + protected boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, @Nullable Craft craft) { + return true; + } + + @Override + protected void onCraftIsBusy(Player player, Craft craft) { } - @EventHandler - public void onSignChange(@NotNull SignChangeEvent event) { - if (HEADER.equalsIgnoreCase(event.getLine(0)) - && Settings.RequireNamePerm && !event.getPlayer().hasPermission("movecraft.name.place")) { + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + if (this.canPlayerUseSign(Action.RIGHT_CLICK_BLOCK, null, event.getPlayer())) { + // Nothing to do + return true; + } else { event.getPlayer().sendMessage(ChatUtils.MOVECRAFT_COMMAND_PREFIX + "Insufficient permissions"); event.setCancelled(true); + return false; } } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClickEvent(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - Block block = event.getClickedBlock(); - if (!(block.getState() instanceof Sign)) { - return; + @Override + public void onCraftDetect(CraftDetectEvent event, AbstractSignListener.SignWrapper sign) { + Craft craft = event.getCraft(); + if (craft != null && craft instanceof PilotedCraft pc) { + if (Settings.RequireNamePerm && !pc.getPilot().hasPermission(NAME_SIGN_PERMISSION)) + return; } - Sign sign = (Sign) block.getState(); - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; - } - event.setCancelled(true); + craft.setName(Arrays.stream(sign.rawLines()).skip(1).filter(f -> f != null + && !f.trim().isEmpty()).collect(Collectors.joining(" "))); } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/PilotSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/PilotSign.java index 9ed6cd230..5853fce65 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/PilotSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/PilotSign.java @@ -1,42 +1,55 @@ package net.countercraft.movecraft.sign; -import org.bukkit.ChatColor; -import org.bukkit.block.Block; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import net.countercraft.movecraft.craft.Craft; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; import org.bukkit.event.block.SignChangeEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public final class PilotSign implements Listener { - private static final String HEADER = "Pilot:"; - @EventHandler - public final void onSignChange(SignChangeEvent event){ - if (event.getLine(0).equalsIgnoreCase(HEADER)) { - String pilotName = ChatColor.stripColor(event.getLine(1)); - if (pilotName.isEmpty()) { - event.setLine(1, event.getPlayer().getName()); - } - } +// TODO: Replace PilotSignValidator with this? +public class PilotSign extends AbstractMovecraftSign { + + public PilotSign() { + super(null); } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClickEvent(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - Block block = event.getClickedBlock(); - if (!(block.getState() instanceof Sign)) { - return; - } + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + return processingSuccessful || !sneaking; + } - Sign sign = (Sign) block.getState(); - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; + // Pilot signs are pretty much always valid + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + return true; + } + + @Override + protected boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, @javax.annotation.Nullable Craft craft) { + // Nothing to do here + return true; + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + boolean foundSome = false; + for (int i = 1; i < sign.lines().size(); i++) { + String data = null; + try { + data = sign.getRaw(i); + } catch (IndexOutOfBoundsException ioob) { + // Ignore + } + if (data != null) { + foundSome = !data.isBlank(); + if (foundSome) { + break; + } + } + } + if (!foundSome) { + sign.line(1, event.getPlayer().name()); } - event.setCancelled(true); + return true; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/RelativeMoveSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/RelativeMoveSign.java index 2e187d3fb..6c63c604e 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/RelativeMoveSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/RelativeMoveSign.java @@ -1,113 +1,65 @@ package net.countercraft.movecraft.sign; -import net.countercraft.movecraft.craft.CraftManager; +import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.type.CraftType; -import net.countercraft.movecraft.localisation.I18nSupport; -import org.bukkit.ChatColor; -import org.bukkit.block.Block; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; -public final class RelativeMoveSign implements Listener{ - private static final String HEADER = "RMove:"; +import javax.annotation.Nullable; - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - Block block = event.getClickedBlock(); - if (!(block.getState() instanceof Sign)) { - return; - } - Sign sign = (Sign) event.getClickedBlock().getState(); - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; - } - event.setCancelled(true); - if (CraftManager.getInstance().getCraftByPlayer(event.getPlayer()) == null) { - return; - } - /*Long time = timeMap.get(event.getPlayer()); - if (time != null) { - long ticksElapsed = (System.currentTimeMillis() - time) / 50; - Craft craft = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - // if the craft should go slower underwater, make time pass - // more slowly there - if (craft.getType().getHalfSpeedUnderwater() && craft.getMinY() < craft.getW().getSeaLevel()) { - ticksElapsed = ticksElapsed >> 1; - } +public class RelativeMoveSign extends MoveSign { + + public RelativeMoveSign() { + super(); + } + + @Override + protected boolean translateCraft(byte signDataRaw, int dxRaw, int dyRaw, int dzRaw, Craft craft, AbstractSignListener.SignWrapper signWrapper) { + final int maxMove = craft.getType().getIntProperty(CraftType.MAX_STATIC_MOVE); - if (Math.abs(ticksElapsed) < CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getTickCooldown()) { - event.setCancelled(true); - return; - } - }*/ - String[] numbers = ChatColor.stripColor(sign.getLine(1)).split(","); - int dLeftRight = Integer.parseInt(numbers[0]); // negative = - // left, - // positive = - // right - int dy = Integer.parseInt(numbers[1]); - int dBackwardForward = Integer.parseInt(numbers[2]); // negative - // = - // backwards, - // positive - // = - // forwards - int maxMove = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getIntProperty(CraftType.MAX_STATIC_MOVE); + // X: Left/Right + // Y: Up/Down + // Z: Forward/Backward - if (dLeftRight > maxMove) - dLeftRight = maxMove; - if (dLeftRight < -maxMove) - dLeftRight = -maxMove; - if (dy > maxMove) - dy = maxMove; - if (dy < -maxMove) - dy = -maxMove; - if (dBackwardForward > maxMove) - dBackwardForward = maxMove; - if (dBackwardForward < -maxMove) - dBackwardForward = -maxMove; + if (dxRaw > maxMove) + dxRaw = maxMove; + if (dxRaw < -maxMove) + dxRaw = -maxMove; + if (dyRaw > maxMove) + dyRaw = maxMove; + if (dyRaw < -maxMove) + dyRaw = -maxMove; + if (dzRaw > maxMove) + dzRaw = maxMove; + if (dzRaw < -maxMove) + dzRaw = -maxMove; int dx = 0; int dz = 0; - switch (sign.getRawData()) { + switch (signDataRaw) { case 0x3: // North - dx = dLeftRight; - dz = -dBackwardForward; + dx = dxRaw; + dz = -dzRaw; break; case 0x2: // South - dx = -dLeftRight; - dz = dBackwardForward; + dx = -dxRaw; + dz = dzRaw; break; case 0x4: // East - dx = dBackwardForward; - dz = dLeftRight; + dx = dzRaw; + dz = dxRaw; break; case 0x5: // West - dx = -dBackwardForward; - dz = -dLeftRight; + dx = -dzRaw; + dz = -dxRaw; break; } - if (!event.getPlayer().hasPermission("movecraft." + CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getStringProperty(CraftType.NAME) + ".move")) { - event.getPlayer().sendMessage( - I18nSupport.getInternationalisedString("Insufficient Permissions")); - return; - } - if (CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getBoolProperty(CraftType.CAN_STATIC_MOVE)) { - CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).translate(dx, dy, dz); - //timeMap.put(event.getPlayer(), System.currentTimeMillis()); - CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).setLastCruiseUpdate(System.currentTimeMillis()); - } + craft.translate(dx, dyRaw, dz); + //timeMap.put(event.getPlayer(), System.currentTimeMillis()); + craft.setLastCruiseUpdate(System.currentTimeMillis()); + + return true; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/ReleaseSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/ReleaseSign.java index 91e9aa544..44c43e754 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/ReleaseSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/ReleaseSign.java @@ -3,37 +3,44 @@ import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.events.CraftReleaseEvent; -import org.bukkit.ChatColor; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.bukkit.event.block.SignChangeEvent; +import org.jetbrains.annotations.Nullable; -public final class ReleaseSign implements Listener{ - private static final String HEADER = "Release"; +public class ReleaseSign extends AbstractMovecraftSign { - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) { - return; - } - Sign sign = (Sign) state; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; + public ReleaseSign() { + super(null); + } + + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + if (processingSuccessful) { + return true; } - event.setCancelled(true); - Craft craft = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); + return !sneaking; + } + + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + return true; + } + + @Override + protected boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, @Nullable Craft craft) { if (craft == null) { - return; + craft = CraftManager.getInstance().getCraftByPlayer(player); + } + if (craft != null) { + CraftManager.getInstance().release(craft, CraftReleaseEvent.Reason.PLAYER, false); } - CraftManager.getInstance().release(craft, CraftReleaseEvent.Reason.PLAYER, false); + return true; } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + return false; + } + } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/RemoteSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/RemoteSign.java index f9bad8835..f847cc530 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/RemoteSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/RemoteSign.java @@ -1,152 +1,164 @@ package net.countercraft.movecraft.sign; +import net.countercraft.movecraft.Movecraft; import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.config.Settings; import net.countercraft.movecraft.craft.Craft; -import net.countercraft.movecraft.craft.CraftManager; -import net.countercraft.movecraft.craft.PlayerCraft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.localisation.I18nSupport; -import net.countercraft.movecraft.util.MathUtils; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.block.Block; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.block.BlockState; import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; import org.bukkit.event.block.SignChangeEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Hashtable; import java.util.LinkedList; +import java.util.Map; +import java.util.Optional; import static net.countercraft.movecraft.util.ChatUtils.ERROR_PREFIX; -public final class RemoteSign implements Listener{ +public class RemoteSign extends AbstractCraftSign { private static final String HEADER = "Remote Sign"; - @EventHandler - public final void onSignChange(SignChangeEvent event) { - if (!event.getLine(0).equalsIgnoreCase(HEADER)) { - return; - } - else if(event.getLine(1).equals("")) { - event.getPlayer().sendMessage(ERROR_PREFIX + I18nSupport.getInternationalisedString("Remote Sign - Cannot be blank")); - event.setLine(0,""); - event.setLine(2,""); - event.setLine(3,""); - return; - } + public RemoteSign() { + super(null, false); } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.LEFT_CLICK_BLOCK) { - return; - } - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) { - return; - } - Sign sign = (Sign) state; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; - } - event.setCancelled(true); - Craft foundCraft = null; - for (PlayerCraft tcraft : CraftManager.getInstance().getPlayerCraftsInWorld(event.getClickedBlock().getWorld())) { - if (MathUtils.locationInHitBox(tcraft.getHitBox(), event.getClickedBlock().getLocation())) { - // don't use a craft with a null player. This is - // mostly to avoid trying to use subcrafts - foundCraft = tcraft; - break; - } - } - - if (foundCraft == null) { - event.getPlayer().sendMessage(ERROR_PREFIX+I18nSupport.getInternationalisedString("Remote Sign - Must be a part of a piloted craft")); - return; - } - - if (!foundCraft.getType().getBoolProperty(CraftType.ALLOW_REMOTE_SIGN)) { - event.getPlayer().sendMessage(ERROR_PREFIX + I18nSupport.getInternationalisedString("Remote Sign - Not allowed on this craft")); - return; - } - - String targetText = ChatColor.stripColor(sign.getLine(1)); - if(targetText.equalsIgnoreCase(HEADER)) { - event.getPlayer().sendMessage(ERROR_PREFIX+I18nSupport.getInternationalisedString("Remote Sign - Cannot remote another Remote Sign")); - return; - } + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + // TODO: How to react? + } - if(targetText.equalsIgnoreCase("")) { - event.getPlayer().sendMessage("Remote Sign - Cannot be blank"); - return; - } + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + player.sendMessage(ERROR_PREFIX+I18nSupport.getInternationalisedString("Remote Sign - Must be a part of a piloted craft")); + } + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { LinkedList foundLocations = new LinkedList(); + Map> foundTargetSigns = new Hashtable<>(); boolean firstError = true; - for (MovecraftLocation tloc : foundCraft.getHitBox()) { - BlockState tstate = event.getClickedBlock().getWorld().getBlockAt(tloc.getX(), tloc.getY(), tloc.getZ()).getState(); + final String targetIdent = sign.getRaw(1).toUpperCase(); + for (MovecraftLocation tloc : craft.getHitBox()) { + BlockState tstate = craft.getWorld().getBlockAt(tloc.getX(), tloc.getY(), tloc.getZ()).getState(); if (!(tstate instanceof Sign)) { continue; } Sign ts = (Sign) tstate; - if (isEqualSign(ts, targetText)) { - if (isForbidden(ts)) { - if (firstError) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Remote Sign - Forbidden string found")); - firstError = false; + AbstractSignListener.SignWrapper[] targetSignWrappers = Movecraft.getInstance().getAbstractSignListener().getSignWrappers(ts); + + if (targetSignWrappers != null) { + for (AbstractSignListener.SignWrapper wrapper : targetSignWrappers) { + // Matches source? + final String signHeader = PlainTextComponentSerializer.plainText().serialize(wrapper.line(0)); + Optional signHandler = AbstractMovecraftSign.tryGet(signHeader); + // Ignore other remove signs + if (!signHandler.isPresent() || signHandler.get() instanceof RemoteSign) { + continue; + } + // Forbidden strings + if (hasForbiddenString(wrapper)) { + if (firstError) { + player.sendMessage(I18nSupport.getInternationalisedString("Remote Sign - Forbidden string found")); + firstError = false; + } + player.sendMessage(" - ".concat(tloc.toString()).concat(" : ").concat(ts.getLine(0))); + } + // But does it match the source man? + if (matchesDescriptor(targetIdent, wrapper)) { + LinkedList value = foundTargetSigns.getOrDefault(signHandler.get(), new LinkedList<>()); + value.add(wrapper); + foundLocations.add(tloc); } - event.getPlayer().sendMessage(" - ".concat(tloc.toString()).concat(" : ").concat(ts.getLine(0))); - } else { - foundLocations.add(tloc); } } } if (!firstError) { - return; + return false; } - else if (foundLocations.isEmpty()) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Remote Sign - Could not find target sign")); - return; + else if (foundTargetSigns.isEmpty()) { + player.sendMessage(I18nSupport.getInternationalisedString("Remote Sign - Could not find target sign")); + return false; } if (Settings.MaxRemoteSigns > -1) { - int foundLocCount = foundLocations.size(); + int foundLocCount = foundTargetSigns.size(); if(foundLocCount > Settings.MaxRemoteSigns) { - event.getPlayer().sendMessage(String.format(I18nSupport.getInternationalisedString("Remote Sign - Exceeding maximum allowed"), foundLocCount, Settings.MaxRemoteSigns)); - return; + player.sendMessage(String.format(I18nSupport.getInternationalisedString("Remote Sign - Exceeding maximum allowed"), foundLocCount, Settings.MaxRemoteSigns)); + return false; } } - for (MovecraftLocation foundLoc : foundLocations) { - Block newBlock = event.getClickedBlock().getWorld().getBlockAt(foundLoc.getX(), foundLoc.getY(), foundLoc.getZ()); + // call the handlers! + foundTargetSigns.entrySet().forEach(entry -> { + AbstractMovecraftSign signHandler = entry.getKey(); + for (AbstractSignListener.SignWrapper wrapper : entry.getValue()) { + signHandler.processSignClick(clickType, wrapper, player); + } + }); + + return true; + } + + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + return processingSuccessful || !sneaking; + } - PlayerInteractEvent newEvent = new PlayerInteractEvent(event.getPlayer(), event.getAction(), event.getItem(), newBlock, event.getBlockFace()); + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + String target = sign.getRaw(1); + if (target.isBlank()) { + player.sendMessage(ERROR_PREFIX + I18nSupport.getInternationalisedString("Remote Sign - Cannot be blank")); + return false; + } - //TODO: DON'T DO THIS - Bukkit.getServer().getPluginManager().callEvent(newEvent); + if (hasForbiddenString(sign)) { + player.sendMessage(I18nSupport.getInternationalisedString("Remote Sign - Forbidden string found")); + return false; } - - event.setCancelled(true); + + return true; } - private boolean isEqualSign(Sign test, String target) { - return !ChatColor.stripColor(test.getLine(0)).equalsIgnoreCase(HEADER) && ( ChatColor.stripColor(test.getLine(0)).equalsIgnoreCase(target) - || ChatColor.stripColor(test.getLine(1)).equalsIgnoreCase(target) - || ChatColor.stripColor(test.getLine(2)).equalsIgnoreCase(target) - || ChatColor.stripColor(test.getLine(3)).equalsIgnoreCase(target) ); + + protected static boolean hasForbiddenString(AbstractSignListener.SignWrapper wrapper) { + for (int i = 0; i < wrapper.lines().size(); i++) { + String s = wrapper.getRaw(i).toLowerCase(); + if(Settings.ForbiddenRemoteSigns.contains(s)) + return true; + } + return false; } - private boolean isForbidden(Sign test) { - for (int i = 0; i < 4; i++) { - String t = test.getLine(i).toLowerCase(); - if(Settings.ForbiddenRemoteSigns.contains(t)) + + // Walks through all strings on the wrapper and if any of the non-header strings match it returns true + protected static boolean matchesDescriptor(final String descriptor, final AbstractSignListener.SignWrapper potentialTarget) { + for (int i = 1; i < potentialTarget.lines().size(); i++) { + String targetStr = potentialTarget.getRaw(i).toUpperCase(); + if (descriptor.equalsIgnoreCase(targetStr)) { return true; + } } return false; } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + return isSignValid(Action.PHYSICAL, sign, event.getPlayer()); + } + + @Override + protected boolean canPlayerUseSignOn(Player player, @Nullable Craft craft) { + if (!craft.getType().getBoolProperty(CraftType.ALLOW_REMOTE_SIGN)) { + player.sendMessage(ERROR_PREFIX + I18nSupport.getInternationalisedString("Remote Sign - Not allowed on this craft")); + return false; + } + + return super.canPlayerUseSignOn(player, craft); + } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/ScuttleSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/ScuttleSign.java index 39f9fd215..07efe8f10 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/ScuttleSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/ScuttleSign.java @@ -2,82 +2,93 @@ import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.CraftManager; +import net.countercraft.movecraft.craft.PilotedCraft; import net.countercraft.movecraft.craft.SinkingCraft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.events.CraftScuttleEvent; import net.countercraft.movecraft.localisation.I18nSupport; -import net.countercraft.movecraft.util.MathUtils; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; +import org.bukkit.event.block.SignChangeEvent; +import org.jetbrains.annotations.Nullable; import static net.countercraft.movecraft.util.ChatUtils.MOVECRAFT_COMMAND_PREFIX; -public class ScuttleSign implements Listener { +public class ScuttleSign extends AbstractCraftSign { - private static final String HEADER = "Scuttle"; + public ScuttleSign() { + super("movecraft.commands.scuttle.others", true); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) - return; - if(event.getClickedBlock() == null) - return; + @Override + protected void onCraftIsBusy(Player player, Craft craft) { - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) { - return; - } - Sign sign = (Sign) state; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; - } - event.setCancelled(true); - Craft craft = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (craft == null) { - if (!event.getPlayer().hasPermission("movecraft.commands.scuttle.others")) { - event.getPlayer().sendMessage(MOVECRAFT_COMMAND_PREFIX - + I18nSupport.getInternationalisedString("You must be piloting a craft")); - return; - } - craft = MathUtils.fastNearestCraftToLoc(CraftManager.getInstance().getCrafts(), - event.getClickedBlock().getLocation()); - if (craft == null) - return; + } + + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + player.sendMessage(MOVECRAFT_COMMAND_PREFIX + + I18nSupport.getInternationalisedString("You must be piloting a craft")); + } + + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + if (processingSuccessful) { + return true; } - scuttle(craft, event.getPlayer()); + return !sneaking; } - private void scuttle(Craft craft, CommandSender commandSender){ + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + return true; + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + return false; + } + + @Override + protected boolean canPlayerUseSignOn(Player player, Craft craft) { if(craft instanceof SinkingCraft) { - commandSender.sendMessage(MOVECRAFT_COMMAND_PREFIX + player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("Scuttle - Craft Already Sinking")); - return; + return false; } - if(!commandSender.hasPermission("movecraft." + craft.getType().getStringProperty(CraftType.NAME) + if(!player.hasPermission("movecraft." + craft.getType().getStringProperty(CraftType.NAME) + ".scuttle")) { - commandSender.sendMessage(MOVECRAFT_COMMAND_PREFIX + player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("Insufficient Permissions")); - return; + return false; + } + if (craft instanceof PilotedCraft pc) { + if (player == pc.getPilot()) { + return true; + } } + // Check for "can scuttle others" permission + if (this.permissionString != null || !this.permissionString.isBlank()) { + if (!player.hasPermission(this.permissionString)) { + player.sendMessage(MOVECRAFT_COMMAND_PREFIX + + I18nSupport.getInternationalisedString("You must be piloting a craft")); + } + } + return true; + } - CraftScuttleEvent e = new CraftScuttleEvent(craft, (Player) commandSender); + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { + CraftScuttleEvent e = new CraftScuttleEvent(craft, player); Bukkit.getServer().getPluginManager().callEvent(e); if(e.isCancelled()) - return; + return false; craft.setCruising(false); CraftManager.getInstance().sink(craft); - commandSender.sendMessage(MOVECRAFT_COMMAND_PREFIX + player.sendMessage(MOVECRAFT_COMMAND_PREFIX + I18nSupport.getInternationalisedString("Scuttle - Scuttle Activated")); + return true; } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/SpeedSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/SpeedSign.java index 1d77fee88..a26f80495 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/SpeedSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/SpeedSign.java @@ -1,89 +1,32 @@ package net.countercraft.movecraft.sign; -import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.craft.Craft; -import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.craft.type.CraftType; -import net.countercraft.movecraft.events.CraftDetectEvent; -import net.countercraft.movecraft.events.SignTranslateEvent; import net.countercraft.movecraft.localisation.I18nSupport; +import net.kyori.adventure.text.Component; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TextComponent; -import org.bukkit.ChatColor; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; +import org.jetbrains.annotations.Nullable; -public final class SpeedSign implements Listener{ - @EventHandler - public void onCraftDetect(CraftDetectEvent event){ - World world = event.getCraft().getWorld(); - for(MovecraftLocation location: event.getCraft().getHitBox()){ - var block = location.toBukkit(world).getBlock(); - if(!Tag.SIGNS.isTagged(block.getType())) - continue; +public class SpeedSign extends AbstractInformationSign { - BlockState state = block.getState(); - if (!(state instanceof Sign)) - continue; - - Sign sign = (Sign) state; - if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Speed:")) { - sign.setLine(1, "0 m/s"); - sign.setLine(2, "0ms"); - sign.setLine(3, "0T"); - sign.update(); - } - } + public SpeedSign() { + super(); } - @EventHandler - public void onSignTranslate(SignTranslateEvent event) { - Craft craft = event.getCraft(); - if (!ChatColor.stripColor(event.getLine(0)).equalsIgnoreCase("Speed:")) { - return; - } - event.setLine(1,String.format("%.2f",craft.getSpeed()) + "m/s"); - event.setLine(2,String.format("%.2f",craft.getMeanCruiseTime() * 1000) + "ms"); - event.setLine(3,craft.getTickCooldown() + "T"); - } - - @EventHandler - public void onSignClick(PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) - return; - - Block block = event.getClickedBlock(); - if (block == null) - return; - - BlockState state = block.getState(); - if (!(state instanceof Sign)) - return; - - Sign sign = (Sign) state; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("Speed:")) - return; - - event.setCancelled(true); - Player player = event.getPlayer(); - Craft craft = CraftManager.getInstance().getCraftByPlayer(player); - if (craft == null) - return; + @Override + protected boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, Craft craft) { + if (clickType != Action.RIGHT_CLICK_BLOCK) + return false; final int gearShifts = craft.getType().getIntProperty(CraftType.GEAR_SHIFTS); int currentGear = craft.getCurrentGear(); if (gearShifts == 1) { player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(I18nSupport.getInternationalisedString("Gearshift - Disabled for craft type"))); - return; + return false; } currentGear++; if (currentGear > gearShifts) @@ -92,5 +35,54 @@ public void onSignClick(PlayerInteractEvent event) { new TextComponent(I18nSupport.getInternationalisedString("Gearshift - Gear changed") + " " + currentGear + " / " + gearShifts)); craft.setCurrentGear(currentGear); + + return true; } + + @Override + protected @Nullable Component getUpdateString(int lineIndex, Component oldData, Craft craft) { + // TODO: Display the gear somewhere? + switch(lineIndex) { + case 1: + return Component.text(String.format("%.2f",craft.getSpeed()) + "m/s"); + case 2: + return Component.text(String.format("%.2f",craft.getMeanCruiseTime() * 1000) + "ms"); + case 3: + return Component.text(craft.getTickCooldown() + "T"); + default: + return null; + } + } + + static final Component[] DEFAULT_VALUES = new Component[] { + null, + Component.text("0 m/s"), + Component.text("0 ms"), + Component.text("0 T") + }; + + @Override + protected @Nullable Component getDefaultString(int lineIndex, Component oldComponent) { + // TODO: Display the gear somewhere? + return DEFAULT_VALUES[lineIndex]; + } + + @Override + protected void performUpdate(Component[] newComponents, AbstractSignListener.SignWrapper sign, REFRESH_CAUSE refreshCause) { + for (int i = 0; i < newComponents.length; i++) { + Component newComp = newComponents[i]; + if (newComp != null) { + sign.line(i, newComp); + } + } + if (refreshCause != REFRESH_CAUSE.SIGN_MOVED_BY_CRAFT && sign.block() != null) { + sign.block().update(true); + } + } + + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + return; + } + } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/SubcraftRotateSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/SubcraftRotateSign.java index c8469fa38..628f4bb23 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/SubcraftRotateSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/SubcraftRotateSign.java @@ -10,93 +10,34 @@ import net.countercraft.movecraft.localisation.I18nSupport; import net.countercraft.movecraft.processing.functions.Result; import net.countercraft.movecraft.util.Pair; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.HashSet; -import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; -public final class SubcraftRotateSign implements Listener { - private static final String HEADER = "Subcraft Rotate"; - private final Set rotating = new HashSet<>(); +public class SubcraftRotateSign extends AbstractSubcraftSign { - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - MovecraftRotation rotation; - if (event.getAction() == Action.RIGHT_CLICK_BLOCK) - rotation = MovecraftRotation.CLOCKWISE; - else if (event.getAction() == Action.LEFT_CLICK_BLOCK) - rotation = MovecraftRotation.ANTICLOCKWISE; - else - return; - - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) - return; - Sign sign = (Sign) state; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) - return; - - event.setCancelled(true); - Location loc = event.getClickedBlock().getLocation(); - MovecraftLocation startPoint = new MovecraftLocation(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); - if (rotating.contains(startPoint)) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Rotation - Already Rotating")); - event.setCancelled(true); - return; - } - - // rotate subcraft - String craftTypeStr = ChatColor.stripColor(sign.getLine(1)); - CraftType craftType = CraftManager.getInstance().getCraftTypeFromString(craftTypeStr); - if (craftType == null) - return; - if (ChatColor.stripColor(sign.getLine(2)).equals("") - && ChatColor.stripColor(sign.getLine(3)).equals("")) { - sign.setLine(2, "_\\ /_"); - sign.setLine(3, "/ \\"); - sign.update(false, false); - } + public SubcraftRotateSign(Function craftTypeRetrievalFunction, Supplier plugin) { + super(craftTypeRetrievalFunction, plugin); + } - if (!event.getPlayer().hasPermission("movecraft." + craftTypeStr + ".pilot") || !event.getPlayer().hasPermission("movecraft." + craftTypeStr + ".rotate")) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); + @Override + protected void runDetectTask(Action clickType, CraftType subcraftType, Craft parentcraft, World world, Player player, MovecraftLocation startPoint) { + final MovecraftRotation rotation = MovecraftRotation.fromAction(clickType); + if (rotation == MovecraftRotation.NONE) { return; } - Craft playerCraft = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (playerCraft != null) { - if (!playerCraft.isNotProcessing()) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Detection - Parent Craft is busy")); - return; - } - playerCraft.setProcessing(true); // prevent the parent craft from moving or updating until the subcraft is done - new BukkitRunnable() { - @Override - public void run() { - playerCraft.setProcessing(false); - } - }.runTaskLater(Movecraft.getInstance(), (10)); - } - - rotating.add(startPoint); - - Player player = event.getPlayer(); - World world = event.getClickedBlock().getWorld(); CraftManager.getInstance().detect( startPoint, - craftType, (type, w, p, parents) -> { + subcraftType, (type, w, p, parents) -> { if (parents.size() > 1) return new Pair<>(Result.failWithMessage(I18nSupport.getInternationalisedString( "Detection - Failed - Already commanding a craft")), null); @@ -107,34 +48,72 @@ public void run() { return new Pair<>(Result.succeed(), new SubCraftImpl(type, w, parent)); }, world, player, player, - craft -> () -> { - Bukkit.getServer().getPluginManager().callEvent(new CraftPilotEvent(craft, CraftPilotEvent.Reason.SUB_CRAFT)); - if (craft instanceof SubCraft) { // Subtract craft from the parent - Craft parent = ((SubCraft) craft).getParent(); - var newHitbox = parent.getHitBox().difference(craft.getHitBox());; + subcraft -> () -> { + Bukkit.getServer().getPluginManager().callEvent(new CraftPilotEvent(subcraft, CraftPilotEvent.Reason.SUB_CRAFT)); + if (subcraft instanceof SubCraft) { // Subtract craft from the parent + Craft parent = ((SubCraft) subcraft).getParent(); + var newHitbox = parent.getHitBox().difference(subcraft.getHitBox());; parent.setHitBox(newHitbox); } new BukkitRunnable() { @Override public void run() { - craft.rotate(rotation, startPoint, true); - if (craft instanceof SubCraft) { - Craft parent = ((SubCraft) craft).getParent(); - var newHitbox = parent.getHitBox().union(craft.getHitBox()); + subcraft.rotate(rotation, startPoint, true); + if (subcraft instanceof SubCraft) { + Craft parent = ((SubCraft) subcraft).getParent(); + var newHitbox = parent.getHitBox().union(subcraft.getHitBox()); parent.setHitBox(newHitbox); } - CraftManager.getInstance().release(craft, CraftReleaseEvent.Reason.SUB_CRAFT, false); + CraftManager.getInstance().release(subcraft, CraftReleaseEvent.Reason.SUB_CRAFT, false); } }.runTaskLater(Movecraft.getInstance(), 3); } ); - event.setCancelled(true); - new BukkitRunnable() { - @Override - public void run() { - rotating.remove(startPoint); - } - }.runTaskLater(Movecraft.getInstance(), 4); + } + + @Override + protected boolean isActionAllowed(String action) { + return action.toUpperCase().equalsIgnoreCase("ROTATE"); + } + + @Override + protected void onActionAlreadyInProgress(Player player) { + player.sendMessage(I18nSupport.getInternationalisedString("Rotation - Already Rotating")); + } + + static final Component DEFAULT_LINE_3 = Component.text("_\\ / _"); + static final Component DEFAULT_LINE_4 = Component.text("/ \\"); + + @Override + protected Component getDefaultTextFor(int line) { + switch (line) { + case 2: + return DEFAULT_LINE_3; + case 3: + return DEFAULT_LINE_4; + default: + return null; + } + } + + @Override + protected boolean canPlayerUseSignForCraftType(Action clickType, AbstractSignListener.SignWrapper sign, Player player, CraftType subcraftType) { + final String craftTypeStr = subcraftType.getStringProperty(CraftType.NAME).toLowerCase(); + if (!player.hasPermission("movecraft." + craftTypeStr + ".rotate")) { + player.sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); + return false; + } + return true; + } + + @Override + protected void onCraftIsBusy(Player player, Craft craft) { + player.sendMessage(I18nSupport.getInternationalisedString("Detection - Parent Craft is busy")); + } + + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + // Ignored } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/TeleportSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/sign/TeleportSign.java index 4f41f6db5..d923e837f 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/TeleportSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/sign/TeleportSign.java @@ -1,78 +1,63 @@ package net.countercraft.movecraft.sign; import net.countercraft.movecraft.craft.Craft; -import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.localisation.I18nSupport; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.World; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.entity.Player; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.jetbrains.annotations.NotNull; -public final class TeleportSign implements Listener { - private static final String HEADER = "Teleport:"; +public class TeleportSign extends MoveSign { - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onSignClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - BlockState state = event.getClickedBlock().getState(); - if (!(state instanceof Sign)) { - return; - } - Sign sign = (Sign) state; - if (!ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(HEADER)) { - return; - } - event.setCancelled(true); - if (CraftManager.getInstance().getCraftByPlayer(event.getPlayer()) == null) { - return; - } - - int tX = sign.getX(); int tY = sign.getY(); int tZ = sign.getZ(); - String[] numbers = ChatColor.stripColor(sign.getLine(1)).replaceAll(" ", "").split(","); - if (numbers.length >= 3) { - try { - - tX = Integer.parseInt(numbers[0]); - tY = Integer.parseInt(numbers[1]); - tZ = Integer.parseInt(numbers[2]); - - } catch (NumberFormatException e) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Invalid Coordinates")); - return; - } - } - - String w = ChatColor.stripColor(sign.getLine(2)); - World world = Bukkit.getWorld(w); - if (world == null) world = sign.getWorld(); + public TeleportSign() { + super(); + } - if (!event.getPlayer().hasPermission("movecraft." + CraftManager.getInstance().getCraftByPlayer(event.getPlayer()).getType().getStringProperty(CraftType.NAME) + ".move")) { - event.getPlayer().sendMessage(I18nSupport.getInternationalisedString("Insufficient Permissions")); - return; - } - final Craft c = CraftManager.getInstance().getCraftByPlayer(event.getPlayer()); - if (c == null || !c.getType().getBoolProperty(CraftType.CAN_TELEPORT)) { - return; + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (!super.isSignValid(clickType, sign, player)) { + return false; + } + // Check world => If specified it has to exist! + String w = sign.getRaw(2); + if (!w.isBlank()) { + World world = Bukkit.getWorld(w); + return world != null; + } + return true; + } + + @Override + protected boolean translateCraft(byte signDataRaw, int dxRaw, int dyRaw, int dzRaw, Craft craft, AbstractSignListener.SignWrapper signWrapper) { + World world = craft.getWorld(); + String w = signWrapper.getRaw(2); + if (!w.isBlank()) { + world = Bukkit.getWorld(w); } - long timeSinceLastTeleport = System.currentTimeMillis() - c.getLastTeleportTime(); - if (c.getType().getIntProperty(CraftType.TELEPORTATION_COOLDOWN) > 0 && timeSinceLastTeleport < c.getType().getIntProperty(CraftType.TELEPORTATION_COOLDOWN)) { - event.getPlayer().sendMessage(String.format(I18nSupport.getInternationalisedString("Teleportation - Cooldown active"), timeSinceLastTeleport)); - return; + + // Substract the signs location so we get a vector + int dx = dxRaw - signWrapper.block().getX(); + int dy = dyRaw - signWrapper.block().getY(); + int dz = dzRaw - signWrapper.block().getZ(); + craft.translate(world, dx, dy, dz); + craft.setLastTeleportTime(System.currentTimeMillis()); + + return true; + } + + @Override + protected boolean canPlayerUseSignOn(Player player, Craft craft) { + if (super.canPlayerUseSignOn(player, craft)) { + if (craft.getType().getBoolProperty(CraftType.CAN_TELEPORT)) { + long timeSinceLastTeleport = System.currentTimeMillis() - craft.getLastTeleportTime(); + if (craft.getType().getIntProperty(CraftType.TELEPORTATION_COOLDOWN) > 0 && timeSinceLastTeleport < craft.getType().getIntProperty(CraftType.TELEPORTATION_COOLDOWN)) { + player.sendMessage(String.format(I18nSupport.getInternationalisedString("Teleportation - Cooldown active"), timeSinceLastTeleport)); + return false; + } + return true; + } } - int dx = tX - sign.getX(); - int dy = tY - sign.getY(); - int dz = tZ - sign.getZ(); - c.translate(world, dx, dy, dz); - c.setLastTeleportTime(System.currentTimeMillis()); + return false; } } diff --git a/api/src/main/java/net/countercraft/movecraft/MovecraftRotation.java b/api/src/main/java/net/countercraft/movecraft/MovecraftRotation.java index f9b580ee7..49f98781b 100644 --- a/api/src/main/java/net/countercraft/movecraft/MovecraftRotation.java +++ b/api/src/main/java/net/countercraft/movecraft/MovecraftRotation.java @@ -17,6 +17,21 @@ package net.countercraft.movecraft; +import org.bukkit.event.block.Action; + public enum MovecraftRotation { - CLOCKWISE, NONE, ANTICLOCKWISE + CLOCKWISE, NONE, ANTICLOCKWISE; + + public static MovecraftRotation fromAction(Action clickType) { + switch (clickType) { + case LEFT_CLICK_AIR: + case LEFT_CLICK_BLOCK: + return ANTICLOCKWISE; + case RIGHT_CLICK_AIR: + case RIGHT_CLICK_BLOCK: + return CLOCKWISE; + default: + return NONE; + } + } } diff --git a/api/src/main/java/net/countercraft/movecraft/events/SignTranslateEvent.java b/api/src/main/java/net/countercraft/movecraft/events/SignTranslateEvent.java index 5439cfdad..e615f8c5f 100644 --- a/api/src/main/java/net/countercraft/movecraft/events/SignTranslateEvent.java +++ b/api/src/main/java/net/countercraft/movecraft/events/SignTranslateEvent.java @@ -2,46 +2,89 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.sign.AbstractSignListener; +import net.kyori.adventure.text.Component; +import org.bukkit.block.BlockFace; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +// TODO: Rewrite to use the adventure API public class SignTranslateEvent extends CraftEvent{ private static final HandlerList HANDLERS = new HandlerList(); @NotNull private final List locations; - @NotNull private final String[] lines; + @NotNull private final AbstractSignListener.SignWrapper backing; private boolean updated = false; + @Deprecated(forRemoval = true) public SignTranslateEvent(@NotNull Craft craft, @NotNull String[] lines, @NotNull List locations) throws IndexOutOfBoundsException{ super(craft); this.locations = locations; - if(lines.length!=4) - throw new IndexOutOfBoundsException(); - this.lines=lines; + List components = new ArrayList<>(); + for (String s : lines) { + components.add(Component.text(s)); + } + this.backing = new AbstractSignListener.SignWrapper(null, components::get, components, components::set, BlockFace.SELF); + } + + public SignTranslateEvent(@NotNull Craft craft, @NotNull AbstractSignListener.SignWrapper backing, @NotNull List locations) throws IndexOutOfBoundsException{ + super(craft); + this.locations = locations; + this.backing = backing; } @NotNull - @Deprecated + @Deprecated(forRemoval = true) public String[] getLines() { + // TODO: Why does this set it to updated? This is just reading... this.updated = true; - return lines; + return backing.rawLines(); } + @Deprecated(forRemoval = true) public String getLine(int index) throws IndexOutOfBoundsException{ if(index > 3 || index < 0) throw new IndexOutOfBoundsException(); - return lines[index]; + return backing.getRaw(index); } + @Deprecated(forRemoval = true) public void setLine(int index, String line){ if(index > 3 || index < 0) throw new IndexOutOfBoundsException(); this.updated = true; - lines[index]=line; + backing.line(index, Component.text(line)); + } + + public Component line(int index) { + return backing.line(index); + } + + public void line(int index, Component component) { + this.updated = true; + backing.line(index, component); + } + + public String getRaw(int index) { + return backing.getRaw(index); + } + + public String[] rawLines() { + return backing.rawLines(); + } + + public BlockFace facing() { + return backing.facing(); + } + + public List lines() { + return backing.lines(); } + // Bukkit crap @Override public HandlerList getHandlers() { return HANDLERS; diff --git a/api/src/main/java/net/countercraft/movecraft/sign/AbstractCraftPilotSign.java b/api/src/main/java/net/countercraft/movecraft/sign/AbstractCraftPilotSign.java new file mode 100644 index 000000000..c2d80313a --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/sign/AbstractCraftPilotSign.java @@ -0,0 +1,17 @@ +package net.countercraft.movecraft.sign; + +import net.countercraft.movecraft.craft.type.CraftType; + +/* + * Base implementation for all craft pilot signs, does nothing but has the relevant CraftType instance backed + */ +public abstract class AbstractCraftPilotSign extends AbstractMovecraftSign { + + protected final CraftType craftType; + + public AbstractCraftPilotSign(final CraftType craftType) { + super(); + this.craftType = craftType; + } + +} diff --git a/api/src/main/java/net/countercraft/movecraft/sign/AbstractCraftSign.java b/api/src/main/java/net/countercraft/movecraft/sign/AbstractCraftSign.java new file mode 100644 index 000000000..232601e82 --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/sign/AbstractCraftSign.java @@ -0,0 +1,123 @@ +package net.countercraft.movecraft.sign; + +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.craft.PilotedCraft; +import net.countercraft.movecraft.craft.PlayerCraft; +import net.countercraft.movecraft.events.CraftDetectEvent; +import net.countercraft.movecraft.events.SignTranslateEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; + +import javax.annotation.Nullable; +import java.util.Optional; + +/* + * Extension of @AbstractMovecraftSign + * The difference is, that for this sign to work, it must exist on a craft instance + * + * Also this will react to the SignTranslate event + */ +public abstract class AbstractCraftSign extends AbstractMovecraftSign { + + // Helper method for the listener + public static Optional tryGetCraftSign(final Component ident) { + if (ident == null) { + return Optional.empty(); + } + final String identStr = PlainTextComponentSerializer.plainText().serialize(ident); + return tryGetCraftSign(identStr); + } + + public static Optional tryGetCraftSign(final String ident) { + Optional tmp = AbstractCraftSign.tryGet(ident); + if (tmp.isPresent() && tmp.get() instanceof AbstractCraftSign acs) { + return Optional.of(acs); + } + return Optional.empty(); + } + + protected final boolean ignoreCraftIsBusy; + + public AbstractCraftSign(boolean ignoreCraftIsBusy) { + this(null, ignoreCraftIsBusy); + } + + public AbstractCraftSign(final String permission, boolean ignoreCraftIsBusy) { + super(permission); + this.ignoreCraftIsBusy = ignoreCraftIsBusy; + } + + // Similar to the super class variant + // In addition a check for a existing craft is being made + // If the craft is a player craft that is currently processing and ignoreCraftIsBusy is set to false, this will quit early and call onCraftIsBusy() + // If no craft is found, onCraftNotFound() is called + // Return true to cancel the event + @Override + public boolean processSignClick(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (!this.isSignValid(clickType, sign, player)) { + return false; + } + if (!this.canPlayerUseSign(clickType, sign, player)) { + return false; + } + Craft craft = this.getCraft(sign); + if (craft == null) { + this.onCraftNotFound(player, sign); + return false; + } + + if (craft instanceof PlayerCraft pc) { + if (!pc.isNotProcessing() && !this.ignoreCraftIsBusy) { + this.onCraftIsBusy(player, craft); + return false; + } + } + + return internalProcessSign(clickType, sign, player, craft); + } + + // Implementation of the standard method. + // The craft instance is required here and it's existance is being confirmed in processSignClick() in beforehand + // After that, canPlayerUseSignOn() is being called. If that is successful, the result of internalProcessSignWithCraft() is returned + @Override + protected boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, @Nullable Craft craft) { + if (craft == null) { + throw new IllegalStateException("Somehow craft is not set here. It should always be present here!"); + } + if (this.canPlayerUseSignOn(player, craft)) { + return this.internalProcessSignWithCraft(clickType, sign, craft, player); + } + return false; + } + + // Called when the craft is a player craft and is processing and ignoreCraftIsBusy is set to false + protected abstract void onCraftIsBusy(Player player, Craft craft); + + // Validation method, intended to indicate if a player is allowed to execute a sign action on a mounted craft + // By default, this returns wether or not the player is the pilot of the craft + protected boolean canPlayerUseSignOn(Player player, @Nullable Craft craft) { + if (craft instanceof PilotedCraft pc) { + return pc.getPilot() == player; + } + return true; + } + + // Called when there is no craft instance for this sign + protected abstract void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign); + + // By default we don't react to CraftDetectEvent here + public void onCraftDetect(CraftDetectEvent event, AbstractSignListener.SignWrapper sign) { + // Do nothing by default + } + + public void onSignMovedByCraft(SignTranslateEvent event) { + // Do nothing by default + } + + // Gets called by internalProcessSign if a craft is found + // Always override this as the validation has been made already when this is being called + protected abstract boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player); + +} diff --git a/api/src/main/java/net/countercraft/movecraft/sign/AbstractCruiseSign.java b/api/src/main/java/net/countercraft/movecraft/sign/AbstractCruiseSign.java new file mode 100644 index 000000000..48b25588a --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/sign/AbstractCruiseSign.java @@ -0,0 +1,169 @@ +package net.countercraft.movecraft.sign; + +import net.countercraft.movecraft.CruiseDirection; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.craft.PilotedCraft; +import net.countercraft.movecraft.events.CraftDetectEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.SignChangeEvent; +import org.jetbrains.annotations.Nullable; + +/* + * Base class for all cruise signs + * + * Has the relevant logic for the "state" suffix (on / off) as well as calling the relevant methods and setting the craft to cruising + * + */ +public abstract class AbstractCruiseSign extends AbstractCraftSign { + + private final String suffixOn; + private final String suffixOff; + private final String ident = AbstractMovecraftSign.findIdent(this); + + public AbstractCruiseSign(boolean ignoreCraftIsBusy, final String suffixOn, final String suffixOff) { + this(null, ignoreCraftIsBusy, suffixOn, suffixOff); + } + + public AbstractCruiseSign(final String permission, boolean ignoreCraftIsBusy, final String suffixOn, final String suffixOff) { + super(permission, ignoreCraftIsBusy); + this.suffixOn = suffixOn; + this.suffixOff = suffixOff; + } + + // Checks if the header is empty, if yes, it quits early (unnecessary actually as if it was empty this would never be called) + // Afterwards the header is validated, if it's splitted variant doesn't have exactly 2 entries it is invalid + // Finally, the "state" (second part of the header) isn't matching suffixOn or suffixOff, it is invalid + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (PlainTextComponentSerializer.plainText().serialize(sign.line(0)).isBlank()) { + return false; + } + String[] headerSplit = getSplitHeader(sign); + if (headerSplit.length != 2) { + return false; + } + String suffix = headerSplit[1]; + return suffix.equalsIgnoreCase(this.suffixOff) || suffix.equalsIgnoreCase(this.suffixOn); + } + + // Returns the raw header, which should consist of the ident and either the suffixOn or suffixOff value + // Returns null if the header is blank + @Nullable + protected static String[] getSplitHeader(final AbstractSignListener.SignWrapper sign) { + String header = PlainTextComponentSerializer.plainText().serialize(sign.line(0)); + if (header.isBlank()) { + return null; + } + return header.split(":"); + } + + // If the suffix matches the suffixOn field it will returnt true + // calls getSplitHeader() to retrieve the raw header string + protected boolean isOnOrOff(AbstractSignListener.SignWrapper sign) { + String[] headerSplit = getSplitHeader(sign); + if (headerSplit == null || headerSplit.length != 2) { + return false; + } + String suffix = headerSplit[1]; + return suffix.equalsIgnoreCase(this.suffixOn); + } + + // By default, cancel the event if the processing was successful, or the invoker was not sneaking => Allows breaking signs while sneaking + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + return processingSuccessful || !sneaking; + } + + // Hook to do stuff that run after stopping to cruise + protected void onAfterStoppingCruise(Craft craft, AbstractSignListener.SignWrapper signWrapper, Player player) { + + } + + // Hook to do stuff that run after starting to cruise + protected void onAfterStartingCruise(Craft craft, AbstractSignListener.SignWrapper signWrapper, Player player) { + + } + + // Actual processing, determines wether the sign will switch to on or off + // If it will be on, the CruiseDirection is retrieved and then setCraftCruising() is called + // Otherwise, the craft will stop cruising + // Then the sign is updated and the block resetted + // Finally, the relevant hooks are called + // This always returns true + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { + boolean isOn = this.isOnOrOff(sign); + boolean willBeOn = !isOn; + if (willBeOn) { + CruiseDirection direction = this.getCruiseDirection(sign); + this.setCraftCruising(player, direction, craft); + } else { + craft.setCruising(false); + } + + // Update sign + sign.line(0, buildHeader(willBeOn)); + sign.block().update(true); + // TODO: What to replace this with? + craft.resetSigns(sign.block()); + + if (willBeOn) { + this.onAfterStartingCruise(craft, sign, player); + } else { + this.onAfterStoppingCruise(craft, sign, player); + } + + return true; + } + + // On sign placement, if the entered header is the same as our ident, it will append the off-suffix automatically + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + String header = sign.getRaw(0).trim(); + if (header.equalsIgnoreCase(this.ident)) { + sign.line(0, buildHeaderOff()); + } + return true; + } + + // On craft detection, we set all the headers to the "off" header + @Override + public void onCraftDetect(CraftDetectEvent event, AbstractSignListener.SignWrapper sign) { + Player p = null; + if (event.getCraft() instanceof PilotedCraft pc) { + p = pc.getPilot(); + } + + if (this.isSignValid(Action.PHYSICAL, sign, p)) { + sign.line(0, buildHeader(false)); + } else { + // TODO: Error? React in any way? + sign.line(0, buildHeader(false)); + } + } + + // Helper method to build the headline for on or off state + protected Component buildHeader(boolean on) { + return on ? buildHeaderOn() : buildHeaderOff(); + } + + protected Component buildHeaderOn() { + return Component.text(this.ident).append(Component.text(": ")).append(Component.text(this.suffixOn, Style.style(TextColor.color(0, 255, 0)))); + } + + protected Component buildHeaderOff() { + return Component.text(this.ident).append(Component.text(": ")).append(Component.text(this.suffixOff, Style.style(TextColor.color(255, 0, 0)))); + } + + // Should call the craft's relevant methods to start cruising + protected abstract void setCraftCruising(Player player, CruiseDirection direction, Craft craft); + + // TODO: Rework cruise direction to vectors => Vector defines the skip distance and the direction + // Returns the direction in which the craft should cruise + protected abstract CruiseDirection getCruiseDirection(AbstractSignListener.SignWrapper sign); +} diff --git a/api/src/main/java/net/countercraft/movecraft/sign/AbstractInformationSign.java b/api/src/main/java/net/countercraft/movecraft/sign/AbstractInformationSign.java new file mode 100644 index 000000000..fed132cb3 --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/sign/AbstractInformationSign.java @@ -0,0 +1,165 @@ +package net.countercraft.movecraft.sign; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.events.CraftDetectEvent; +import net.countercraft.movecraft.events.SignTranslateEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Sign; +import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.SignChangeEvent; +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractInformationSign extends AbstractCraftSign { + + public static final Component EMPTY = Component.text(""); + + protected static final Style STYLE_COLOR_GREEN = Style.style(TextColor.color(0, 255, 0)); + protected static final Style STYLE_COLOR_YELLOW = Style.style(TextColor.color(255, 255, 0)); + protected static final Style STYLE_COLOR_RED = Style.style(TextColor.color(255, 0, 0)); + protected static final Style STYLE_COLOR_WHITE = Style.style(TextColor.color(255, 255, 255)); + + public enum REFRESH_CAUSE { + SIGN_CREATION, + CRAFT_DETECT, + SIGN_MOVED_BY_CRAFT, + SIGN_CLICK + } + + public AbstractInformationSign() { + // Info signs only display things, that should not require permissions, also it doesn't matter if the craft is busy or not + super(null, true); + } + + @Override + protected boolean canPlayerUseSignOn(Player player, Craft craft) { + // Permcheck related, no perms required, return true + return true; + } + + @Override + public void onCraftDetect(CraftDetectEvent event, AbstractSignListener.SignWrapper sign) { + // TODO: Check if the craft supports this sign? If no, cancel + super.onCraftDetect(event, sign); + this.refreshSign(event.getCraft(), sign, true, REFRESH_CAUSE.CRAFT_DETECT); + } + + @Override + public void onSignMovedByCraft(SignTranslateEvent event) { + super.onSignMovedByCraft(event); + final Craft craft = event.getCraft(); + AbstractSignListener.SignWrapper wrapperTmp = new AbstractSignListener.SignWrapper(null, event::line, event.lines(), event::line, BlockFace.SELF); + if (this.refreshSign(craft, wrapperTmp, false, REFRESH_CAUSE.SIGN_MOVED_BY_CRAFT)) { + for (MovecraftLocation movecraftLocation : event.getLocations()) { + Block block = movecraftLocation.toBukkit(craft.getWorld()).getBlock(); + if (block instanceof Sign sign) { + AbstractSignListener.SignWrapper wrapperTmpTmp = new AbstractSignListener.SignWrapper(sign, event::line, event.lines(), event::line, event.facing()); + this.sendUpdatePacket(craft, wrapperTmpTmp, REFRESH_CAUSE.SIGN_MOVED_BY_CRAFT); + } + } + } + } + + @Override + protected void onCraftNotFound(Player player, AbstractSignListener.SignWrapper sign) { + // Nothing to do + } + + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + return true; + } + + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + if (processingSuccessful) { + return true; + } + return !sneaking; + } + + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, Craft craft, Player player) { + if (this.refreshSign(craft, sign, false, REFRESH_CAUSE.SIGN_CLICK)) { + this.sendUpdatePacket(craft, sign, REFRESH_CAUSE.SIGN_CLICK); + } + return true; + } + + // Called whenever the info needs to be refreshed + // That happens on CraftDetect, sign right click (new), Sign Translate + // The new and old values are gathered here and compared + // If nothing has changed, no update happens + // If something has changed, performUpdate() and sendUpdatePacket() are called + // Returns wether or not something has changed + protected boolean refreshSign(@Nullable Craft craft, AbstractSignListener.SignWrapper sign, boolean fillDefault, REFRESH_CAUSE refreshCause) { + boolean changedSome = false; + Component[] updatePayload = new Component[sign.lines().size()]; + for(int i = 1; i < sign.lines().size(); i++) { + Component oldComponent = sign.line(i); + Component potentiallyNew; + if (craft == null || fillDefault) { + potentiallyNew = this.getDefaultString(i, oldComponent); + } else { + potentiallyNew = this.getUpdateString(i, oldComponent, craft); + } + if (potentiallyNew != null && !potentiallyNew.equals(oldComponent)) { + String oldValue = PlainTextComponentSerializer.plainText().serialize(oldComponent); + String newValue = PlainTextComponentSerializer.plainText().serialize(potentiallyNew); + if (!oldValue.equals(newValue)) { + changedSome = true; + updatePayload[i] = potentiallyNew; + } + } + } + if (changedSome) { + this.performUpdate(updatePayload, sign, refreshCause); + } + return changedSome; + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + if (this.refreshSign(null, sign, true, REFRESH_CAUSE.SIGN_CREATION)) { + this.sendUpdatePacket(null, sign, REFRESH_CAUSE.SIGN_CREATION); + } + return true; + } + + /* + Data to set on the sign. Return null if no update should happen! + Attention: A update will only be performed, if the new and old component are different! + */ + @Nullable + protected abstract Component getUpdateString(int lineIndex, Component oldData, Craft craft); + + // Returns the default value for this info sign per line + // Used on CraftDetect and on sign change + @Nullable + protected abstract Component getDefaultString(int lineIndex, Component oldComponent); + + /* + * @param newComponents: Array of nullable values. The index represents the index on the sign. Only contains the updated components + * + * Only gets called if at least one line has changed + */ + protected abstract void performUpdate(Component[] newComponents, AbstractSignListener.SignWrapper sign, REFRESH_CAUSE refreshCause); + + /* + Gets called after performUpdate has been called + */ + protected void sendUpdatePacket(Craft craft, AbstractSignListener.SignWrapper sign, REFRESH_CAUSE refreshCause) { + if (sign.block() == null) { + return; + } + for (Player player : sign.block().getLocation().getNearbyPlayers(16)) { + player.sendSignChange(sign.block().getLocation(), sign.lines()); + } + } +} diff --git a/api/src/main/java/net/countercraft/movecraft/sign/AbstractMovecraftSign.java b/api/src/main/java/net/countercraft/movecraft/sign/AbstractMovecraftSign.java new file mode 100644 index 000000000..516ee6afa --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/sign/AbstractMovecraftSign.java @@ -0,0 +1,160 @@ +package net.countercraft.movecraft.sign; + +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.craft.type.CraftType; +import net.countercraft.movecraft.util.MathUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.SignChangeEvent; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Function; + +// DONE: In 1.21 signs can have multiple sides! This requires us to pass the clicked side through or well the relevant lines and the set method for the clicked side => Resolved using the SignWrapper +/* + * Base class for all signs + * + * A instance of a sign needs to be registered using the register function. + * Signs react to the following events: + * - SignChangeEvent + * - PlayerInteractEvent, if the clicked block is a sign + * - CraftDetectEvent + * - SignTranslateEvent (if the sign is a subclass of AbstractCraftSign) + * + * Whenenver one of those events are cought by the AbstractSignListener instance, it is attempted to retrieve the relevant AbstractMovecraftSign instance. + * For that, the first line of the sign's clicked side is extracted and formatting removed. If it matches the format "foo: bar", only "foo:" will be used. + * With that ident, the sign is attempted to be retrieved vy tryGet(). If that returns something, the object's relevant method is called. + */ +public abstract class AbstractMovecraftSign { + + private static final Map SIGNS = Collections.synchronizedMap(new HashMap<>()); + + public static boolean hasBeenRegistered(final String ident) { + return SIGNS.containsKey(ident); + } + + // Special case for pilot signs, they are registered via the crafttypes name + public static void registerCraftPilotSigns(Set loadedTypes, Function signFactory) { + SIGNS.entrySet().removeIf(entry -> entry.getValue() instanceof AbstractCraftPilotSign); + // Now, add all types... + for (CraftType type : loadedTypes) { + AbstractCraftPilotSign sign = signFactory.apply(type); + register(type.getStringProperty(CraftType.NAME), sign, true); + } + } + + public static Optional tryGet(final Component ident) { + if (ident == null) { + return Optional.empty(); + } + final String identStr = PlainTextComponentSerializer.plainText().serialize(ident); + return tryGet(identStr); + } + + // Attempts to find a AbstractMovecraftSign instance, if something has been registered + // If the ident follows the format "foo: bar", only "foo:" is used as ident to search for + public static Optional tryGet(final String ident) { + String identToUse = ident.toUpperCase(); + if (identToUse.contains(":")) { + identToUse = identToUse.split(":")[0]; + // Re-add the : cause things should be registered with : at the end + identToUse = identToUse + ":"; + } + return Optional.ofNullable(SIGNS.getOrDefault(identToUse, null)); + } + + // Registers a sign in all cases + public static void register(final String ident, final @Nonnull AbstractMovecraftSign instance) { + register(ident, instance, true); + } + + // Registers a sign + // If @param overrideIfAlreadyRegistered is set to false, it won't be registered if something has elready been registered using that name + public static void register(final String ident, final @Nonnull AbstractMovecraftSign instance, boolean overrideIfAlreadyRegistered) { + if (overrideIfAlreadyRegistered) { + SIGNS.put(ident.toUpperCase(), instance); + } else { + SIGNS.putIfAbsent(ident.toUpperCase(), instance); + } + } + + // Optional permission for this sign + // Note that this is only checked against in normal processSignClick by default + // When using the default constructor, the permission will not be set + @Nullable + protected final String permissionString; + + public AbstractMovecraftSign() { + this(null); + } + + public AbstractMovecraftSign(String permissionNode) { + this.permissionString = permissionNode; + } + + // Utility function to retrieve the ident of a a given sign instance + // DO NOT call this for unregistered instances! + // It is a good idea to cache the return value of this function cause otherwise a loop over all registered sign instances will be necessary + public static String findIdent(AbstractMovecraftSign instance) { + if (!SIGNS.containsValue(instance)) { + throw new IllegalArgumentException("MovecraftSign instance must be registered!"); + } + for (Map.Entry entry : SIGNS.entrySet()) { + if (entry.getValue() == instance) { + return entry.getKey(); + } + } + throw new IllegalStateException("Somehow didn't find a key for a value that is in the map!"); + } + + // Called whenever a player clicks the sign + // SignWrapper wraps the relevant clicked side of the sign and the sign block itself + // If true is returned, the event will be cancelled + public boolean processSignClick(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (!this.isSignValid(clickType, sign, player)) { + return false; + } + if (!this.canPlayerUseSign(clickType, sign, player)) { + return false; + } + + return internalProcessSign(clickType, sign, player, getCraft(sign)); + } + + // Validation method + // By default this checks if the player has the set permission + protected boolean canPlayerUseSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (this.permissionString == null || this.permissionString.isBlank()) { + return true; + } + return player.hasPermission(this.permissionString); + } + + // Helper method, simply calls the existing methods + @Nullable + protected Craft getCraft(AbstractSignListener.SignWrapper sign) { + return MathUtils.getCraftByPersistentBlockData(sign.block().getLocation()); + } + + // Used by the event handler to determine if the event should be cancelled + // processingSuccessful is the output of processSignClick() or processSignChange() + // This is only called for the PlayerInteractEvent and the SignChangeEvent + public abstract boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking); + + // Validation method, called by default in processSignClick + // If false is returned, nothing will be processed + protected abstract boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player); + + // Called by processSignClick after validation. At this point, isSignValid() and canPlayerUseSign() have been called already + // If the sign belongs to a craft, that craft is given in the @param craft argument + // Return true, if everything was ok + protected abstract boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, @Nullable Craft craft); + + // Called by the event handler when SignChangeEvent is being cought + // Return true, if everything was ok + public abstract boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign); +} diff --git a/api/src/main/java/net/countercraft/movecraft/sign/AbstractSignListener.java b/api/src/main/java/net/countercraft/movecraft/sign/AbstractSignListener.java new file mode 100644 index 000000000..db3008138 --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/sign/AbstractSignListener.java @@ -0,0 +1,211 @@ +package net.countercraft.movecraft.sign; + +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.events.CraftDetectEvent; +import net.countercraft.movecraft.events.SignTranslateEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/* + * As soon as 1.18 support is dropped, the adapter system will be dropped too + */ +@Deprecated(forRemoval = true) +public abstract class AbstractSignListener implements Listener { + + public static AbstractSignListener INSTANCE; + + public AbstractSignListener() { + INSTANCE = this; + } + + /* + * As soon as 1.18 support is dropped, the adapter system will be dropped too + */ + @Deprecated(forRemoval = true) + public record SignWrapper( + @Nullable Sign block, + Function getLine, + List lines, + BiConsumer setLine, + BlockFace facing + ) { + public Component line(int index) { + if (index >= lines.size() || index < 0) { + throw new IndexOutOfBoundsException(); + } + return getLine().apply(index); + } + + public void line(int index, Component component) { + setLine.accept(index, component); + } + + public String getRaw(int index) { + return PlainTextComponentSerializer.plainText().serialize(line(index)); + } + + public String[] rawLines() { + String[] result = new String[this.lines.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = this.getRaw(i); + } + return result; + } + + public boolean isEmpty() { + for(String s : this.rawLines()) { + if (s.trim().isEmpty() || s.trim().isBlank()) { + continue; + } + else { + return false; + } + } + return true; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj instanceof SignWrapper other) { + return areSignsEqual(other); + } + return false; + } + + public boolean areSignsEqual(SignWrapper other) { + return areSignsEqual(this, other); + } + + public static boolean areSignsEqual(SignWrapper a, SignWrapper b) { + String[] aLines = a.rawLines(); + String[] bLines = b.rawLines(); + + if (aLines.length != bLines.length) { + return false; + } + + for (int i = 0; i < aLines.length; i++) { + String aLine = aLines[i].trim(); + String bLine = bLines[i].trim(); + + if (!aLine.equalsIgnoreCase(bLine)) { + return false; + } + } + + // Now check the facing too! + return a.facing().equals(b.facing()); + } + + public static boolean areSignsEqual(SignWrapper[] a, SignWrapper[] b) { + if (a == null || b == null) { + return false; + } + if (a.length != b.length) { + return false; + } + + for (int i = 0; i < a.length; i++) { + SignWrapper aWrap = a[i]; + SignWrapper bWrap = b[i]; + if (!areSignsEqual(aWrap, bWrap)) { + return false; + } + } + return true; + } + + public void copyContent(SignWrapper other) { + for (int i = 0; i < this.lines().size() && i < other.lines().size(); i++) { + this.line(i, other.line(i)); + } + } + + } + + public SignWrapper[] getSignWrappers(Sign sign) { + return getSignWrappers(sign, false); + }; + public abstract SignWrapper[] getSignWrappers(Sign sign, boolean ignoreEmpty); + protected abstract SignWrapper getSignWrapper(Sign sign, SignChangeEvent signChangeEvent); + protected abstract SignWrapper getSignWrapper(Sign sign, PlayerInteractEvent interactEvent); + + public abstract void processSignTranslation(final Craft craft, boolean checkEventIsUpdated); + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onCraftDetect(CraftDetectEvent event) { + final World world = event.getCraft().getWorld(); + event.getCraft().getHitBox().forEach( + (mloc) -> { + Block block = mloc.toBukkit(world).getBlock(); + BlockState state = block.getState(); + if (state instanceof Sign sign) { + for (SignWrapper wrapper : this.getSignWrappers(sign)) { + AbstractCraftSign.tryGetCraftSign(wrapper.line(0)).ifPresent(acs -> acs.onCraftDetect(event, wrapper)); + } + } + } + ); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onSignTranslate(SignTranslateEvent event) { + AbstractCraftSign.tryGetCraftSign(event.line(0)).ifPresent(acs -> acs.onSignMovedByCraft(event)); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onSignChange(SignChangeEvent event) { + Block block = event.getBlock(); + BlockState state = block.getState(); + if (state instanceof Sign sign) { + SignWrapper wrapper = this.getSignWrapper(sign, event); + AbstractMovecraftSign.tryGet(wrapper.line(0)).ifPresent(ams -> { + + boolean success = ams.processSignChange(event, wrapper); + if (ams.shouldCancelEvent(success, null, event.getPlayer().isSneaking())) { + event.setCancelled(true); + } + }); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onSignClick(PlayerInteractEvent event) { + Block block = event.getClickedBlock(); + if (block == null) { + return; + } + BlockState state = block.getState(); + if (state instanceof Sign sign) { + SignWrapper wrapper = this.getSignWrapper(sign, event); + AbstractMovecraftSign.tryGet(wrapper.line(0)).ifPresent(ams -> { + boolean success = ams.processSignClick(event.getAction(), wrapper, event.getPlayer()); + if (ams.shouldCancelEvent(success, event.getAction(), event.getPlayer().isSneaking())) { + event.setCancelled(true); + } + }); + } + } + +} diff --git a/api/src/main/java/net/countercraft/movecraft/sign/AbstractSubcraftSign.java b/api/src/main/java/net/countercraft/movecraft/sign/AbstractSubcraftSign.java new file mode 100644 index 000000000..cf9d40cb7 --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/sign/AbstractSubcraftSign.java @@ -0,0 +1,178 @@ +package net.countercraft.movecraft.sign; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.craft.PlayerCraft; +import net.countercraft.movecraft.craft.type.CraftType; +import net.kyori.adventure.text.Component; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +public abstract class AbstractSubcraftSign extends AbstractCraftSign { + + // TODO: Replace by writing to the signs nbt data + protected static final Set IN_USE = Collections.synchronizedSet(new HashSet<>()); + + protected final Function craftTypeRetrievalFunction; + + protected final Supplier pluginInstance; + + public AbstractSubcraftSign(Function craftTypeRetrievalFunction, final Supplier plugin) { + this(null, craftTypeRetrievalFunction, plugin); + } + + public AbstractSubcraftSign(final String permission, Function craftTypeRetrievalFunction, final Supplier plugin) { + super(permission, false); + this.craftTypeRetrievalFunction = craftTypeRetrievalFunction; + this.pluginInstance = plugin; + } + + @Override + public boolean processSignClick(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (!this.isSignValid(clickType, sign, player)) { + return false; + } + if (!this.canPlayerUseSign(clickType, sign, player)) { + return false; + } + Craft craft = this.getCraft(sign); + + if (craft instanceof PlayerCraft pc) { + if (!pc.isNotProcessing() && !this.ignoreCraftIsBusy) { + this.onCraftIsBusy(player, craft); + return false; + } + } + + return internalProcessSign(clickType, sign, player, craft); + } + + @Override + protected boolean internalProcessSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player, Craft craft) { + if (craft != null) { + // TODO: Add property to crafts that they can use subcrafts? + if (!this.canPlayerUseSignOn(player, craft)) { + return false; + } + } + return this.internalProcessSignWithCraft(clickType, sign, craft, player); + } + + @Override + public boolean shouldCancelEvent(boolean processingSuccessful, @Nullable Action type, boolean sneaking) { + return processingSuccessful || !sneaking; + } + + @Override + public boolean processSignChange(SignChangeEvent event, AbstractSignListener.SignWrapper sign) { + // TODO: Implement + return false; + } + + @Override + protected boolean isSignValid(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + String[] headerSplit = sign.getRaw(0).split(" "); + if (headerSplit.length != 2) { + return false; + } + // TODO: Change to enums? + String action = headerSplit[headerSplit.length - 1].toUpperCase(); + if (!this.isActionAllowed(action)) { + return false; + } + return this.getCraftType(sign) != null; + } + + @Override + protected boolean canPlayerUseSign(Action clickType, AbstractSignListener.SignWrapper sign, Player player) { + if (!super.canPlayerUseSign(clickType, sign, player)) { + return false; + } + CraftType craftType = this.getCraftType(sign); + if (craftType != null) { + return player.hasPermission("movecraft." + craftType.getStringProperty(CraftType.NAME) + ".pilot") && this.canPlayerUseSignForCraftType(clickType, sign, player, craftType); + } + return false; + } + + @Override + protected boolean internalProcessSignWithCraft(Action clickType, AbstractSignListener.SignWrapper sign, @Nullable Craft craft, Player player) { + CraftType subcraftType = this.getCraftType(sign); + + final Location signLoc = sign.block().getLocation(); + final MovecraftLocation startPoint = new MovecraftLocation(signLoc.getBlockX(), signLoc.getBlockY(), signLoc.getBlockZ()); + + if (craft != null) { + craft.setProcessing(true); + // TODO: SOlve this more elegantly... + new BukkitRunnable() { + @Override + public void run() { + craft.setProcessing(false); + } + }.runTaskLater(this.pluginInstance.get(), (10)); + } + + if (!IN_USE.add(startPoint)) { + this.onActionAlreadyInProgress(player); + return true; + } + + this.applyDefaultText(sign); + + final World world = sign.block().getWorld(); + + this.runDetectTask(clickType, subcraftType, craft, world, player, startPoint); + + // TODO: Change this, it is ugly, should be done by the detect task itself + new BukkitRunnable() { + @Override + public void run() { + IN_USE.remove(startPoint); + } + }.runTaskLater(this.pluginInstance.get(), 4); + + return false; + } + + protected void applyDefaultText(AbstractSignListener.SignWrapper sign) { + if (sign.getRaw(2).isBlank() && sign.getRaw(3).isBlank()) { + Component l3 = this.getDefaultTextFor(2); + Component l4 = this.getDefaultTextFor(3); + if (l3 != null) { + sign.line(2, l3); + } + if (l4 != null) { + sign.line(2, l4); + } + } + } + + @Nullable + protected CraftType getCraftType(AbstractSignListener.SignWrapper wrapper) { + String ident = wrapper.getRaw(2); + if (ident.trim().isBlank()) { + return null; + } + return this.craftTypeRetrievalFunction.apply(ident); + } + + protected abstract void runDetectTask(Action clickType, CraftType subcraftType, Craft parentCraft, World world, Player player, MovecraftLocation startPoint); + protected abstract boolean isActionAllowed(final String action); + protected abstract void onActionAlreadyInProgress(Player player); + protected abstract Component getDefaultTextFor(int line); + protected abstract boolean canPlayerUseSignForCraftType(Action clickType, AbstractSignListener.SignWrapper sign, Player player, CraftType subCraftType); + +} diff --git a/v1_18/src/main/java/net/countercraft/movecraft/compat/v1_18/SignListener.java b/v1_18/src/main/java/net/countercraft/movecraft/compat/v1_18/SignListener.java new file mode 100644 index 000000000..7f1e122a6 --- /dev/null +++ b/v1_18/src/main/java/net/countercraft/movecraft/compat/v1_18/SignListener.java @@ -0,0 +1,136 @@ +package net.countercraft.movecraft.compat.v1_18; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.events.SignTranslateEvent; +import net.countercraft.movecraft.sign.AbstractSignListener; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.block.data.Directional; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +import java.util.*; + +/* + * As soon as 1.18 support is dropped, the adapter system will be dropped too + */ +@Deprecated(forRemoval = true) +public class SignListener extends AbstractSignListener { + + protected final SignWrapper createFromSign(final Sign sign) { + BlockFace face = ((Directional) sign.getBlockData()).getFacing(); + SignWrapper wrapper = new SignWrapper( + sign, + sign::line, + sign.lines(), + sign::line, + face + ); + return wrapper; + } + + @Override + public SignWrapper[] getSignWrappers(Sign sign, boolean ignoreEmpty) { + SignWrapper wrapper = this.createFromSign(sign); + if (ignoreEmpty && wrapper.isEmpty()) { + return new SignWrapper[] {}; + } + return new SignWrapper[] {wrapper}; + } + + @Override + protected SignWrapper getSignWrapper(Sign sign, SignChangeEvent signChangeEvent) { + BlockFace face = ((Directional) sign.getBlockData()).getFacing(); + SignWrapper wrapper = new SignWrapper( + sign, + signChangeEvent::line, + signChangeEvent.lines(), + signChangeEvent::line, + face + ); + return wrapper; + } + + @Override + protected SignWrapper getSignWrapper(Sign sign, PlayerInteractEvent interactEvent) { + return this.createFromSign(sign); + } + + @Override + public void processSignTranslation(Craft craft, boolean checkEventIsUpdated) { + Object2ObjectMap> signs = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { + @Override + public int hashCode(SignWrapper strings) { + return Arrays.hashCode(strings.rawLines()); + } + + @Override + public boolean equals(SignWrapper a, SignWrapper b) { + return SignWrapper.areSignsEqual(a, b); + } + }); + Map signStates = new HashMap<>(); + + for (MovecraftLocation location : craft.getHitBox()) { + Block block = location.toBukkit(craft.getWorld()).getBlock(); + if(!Tag.SIGNS.isTagged(block.getType())){ + continue; + } + BlockState state = block.getState(); + if (state instanceof Sign) { + Sign sign = (Sign) state; + SignWrapper[] wrappersAtLoc = this.getSignWrappers(sign, true); + if (wrappersAtLoc == null || wrappersAtLoc.length == 0) { + continue; + } + for (SignWrapper wrapper : wrappersAtLoc) { + List values = signs.computeIfAbsent(wrapper, (w) -> new ArrayList<>()); + values.add(location); + } + signStates.put(location, wrappersAtLoc); + } + } + // TODO: This is not good yet, this doesn't really care about a signs sides... + for(Map.Entry> entry : signs.entrySet()){ + final List components = new ArrayList<>(entry.getKey().lines()); + SignWrapper backingForEvent = new SignWrapper(null, components::get, components, components::set, entry.getKey().facing()); + SignTranslateEvent event = new SignTranslateEvent(craft, backingForEvent, entry.getValue()); + Bukkit.getServer().getPluginManager().callEvent(event); + // if(!event.isUpdated()){ + // continue; + // } + // TODO: This is implemented only to fix client caching + // ideally we wouldn't do the update and would instead fake it out to the player + for(MovecraftLocation location : entry.getValue()){ + Block block = location.toBukkit(craft.getWorld()).getBlock(); + BlockState state = block.getState(); + if (!(state instanceof Sign)) { + continue; + } + SignWrapper[] signsAtLoc = signStates.get(location); + if (signsAtLoc != null && signsAtLoc.length > 0) { + for (SignWrapper sw : signsAtLoc) { + if (!checkEventIsUpdated || event.isUpdated()) { + sw.copyContent(entry.getKey()); + } + } + try { + ((Sign)location.toBukkit(craft.getWorld()).getBlock()).update(false, false); + } catch(ClassCastException ex) { + // Ignore + } + } + } + } + } + +} diff --git a/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/SignListener.java b/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/SignListener.java new file mode 100644 index 000000000..6a5d9add0 --- /dev/null +++ b/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/SignListener.java @@ -0,0 +1,160 @@ +package net.countercraft.movecraft.compat.v1_20; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.events.SignTranslateEvent; +import net.countercraft.movecraft.sign.AbstractSignListener; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.block.data.Directional; +import org.bukkit.block.sign.Side; +import org.bukkit.block.sign.SignSide; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/* + * As soon as 1.18 support is dropped, the adapter system will be dropped too + */ +@Deprecated(forRemoval = true) +public class SignListener extends AbstractSignListener { + + protected final SignWrapper createFromSide(final Sign sign, final Side side) { + SignSide signSide = sign.getSide(side); + return createFromSide(sign, signSide, side); + } + + protected final SignWrapper createFromSide(final Sign sign, final SignSide signSide, Side side) { + BlockFace face = ((Directional) sign.getBlockData()).getFacing(); + if (side == Side.BACK) { + face = face.getOppositeFace(); + } + SignWrapper wrapper = new SignWrapper( + sign, + signSide::line, + signSide.lines(), + signSide::line, + face + ); + return wrapper; + } + + @Override + public SignWrapper[] getSignWrappers(Sign sign, boolean ignoreEmpty) { + Side[] sides = new Side[Side.values().length]; + List wrappers = new ArrayList<>(); + for (int i = 0; i < sides.length; i++) { + Side side = sides[i]; + SignSide signSide = sign.getSide(side); + SignWrapper wrapper = this.createFromSide(sign, signSide, side); + wrappers.add(wrapper); + } + if (ignoreEmpty) + wrappers.removeIf(SignWrapper::isEmpty); + return wrappers.toArray(new SignWrapper[wrappers.size()]); + } + + @Override + protected SignWrapper getSignWrapper(Sign sign, SignChangeEvent signChangeEvent) { + @NotNull Side side = signChangeEvent.getSide(); + BlockFace face = ((Directional) sign.getBlockData()).getFacing(); + if (side == Side.BACK) { + face = face.getOppositeFace(); + } + SignWrapper wrapper = new SignWrapper( + sign, + signChangeEvent::line, + signChangeEvent.lines(), + signChangeEvent::line, + face + ); + return wrapper; + } + + @Override + protected SignWrapper getSignWrapper(Sign sign, PlayerInteractEvent interactEvent) { + @NotNull SignSide side = sign.getTargetSide(interactEvent.getPlayer()); + return this.createFromSide(sign, side, sign.getInteractableSideFor(interactEvent.getPlayer())); + } + + @Override + public void processSignTranslation(Craft craft, boolean checkEventIsUpdated) { + Object2ObjectMap> signs = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { + @Override + public int hashCode(SignWrapper strings) { + return Arrays.hashCode(strings.rawLines()) * strings.facing().hashCode(); + } + + @Override + public boolean equals(SignWrapper a, SignWrapper b) { + return SignWrapper.areSignsEqual(a, b); + } + }); + Map signStates = new HashMap<>(); + + for (MovecraftLocation location : craft.getHitBox()) { + Block block = location.toBukkit(craft.getWorld()).getBlock(); + if(!Tag.SIGNS.isTagged(block.getType())){ + continue; + } + BlockState state = block.getState(); + if (state instanceof Sign) { + Sign sign = (Sign) state; + SignWrapper[] wrappersAtLoc = this.getSignWrappers(sign, true); + if (wrappersAtLoc == null || wrappersAtLoc.length == 0) { + continue; + } + for (SignWrapper wrapper : wrappersAtLoc) { + List values = signs.computeIfAbsent(wrapper, (w) -> new ArrayList<>()); + values.add(location); + } + signStates.put(location, wrappersAtLoc); + } + } + for(Map.Entry> entry : signs.entrySet()){ + final List components = new ArrayList<>(entry.getKey().lines()); + SignWrapper backingForEvent = new SignWrapper(null, components::get, components, components::set, entry.getKey().facing()); + SignTranslateEvent event = new SignTranslateEvent(craft, backingForEvent, entry.getValue()); + Bukkit.getServer().getPluginManager().callEvent(event); + // if(!event.isUpdated()){ + // continue; + // } + // TODO: This is implemented only to fix client caching + // ideally we wouldn't do the update and would instead fake it out to the player + for(MovecraftLocation location : entry.getValue()){ + Block block = location.toBukkit(craft.getWorld()).getBlock(); + BlockState state = block.getState(); + if (!(state instanceof Sign)) { + continue; + } + SignWrapper[] signsAtLoc = signStates.get(location); + if (signsAtLoc != null && signsAtLoc.length > 0) { + for (SignWrapper sw : signsAtLoc) { + // Important: Check if the wrapper faces the right way! + if (!sw.facing().equals(entry.getKey().facing())) { + continue; + } + if (!checkEventIsUpdated || event.isUpdated()) { + sw.copyContent(entry.getKey()); + } + } + try { + ((Sign)location.toBukkit(craft.getWorld()).getBlock()).update(false, false); + } catch(ClassCastException ex) { + // Ignore + } + } + } + } + } +} diff --git a/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/SignListener.java b/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/SignListener.java new file mode 100644 index 000000000..f49ed798c --- /dev/null +++ b/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/SignListener.java @@ -0,0 +1,160 @@ +package net.countercraft.movecraft.compat.v1_21; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.events.SignTranslateEvent; +import net.countercraft.movecraft.sign.AbstractSignListener; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.block.data.Directional; +import org.bukkit.block.sign.Side; +import org.bukkit.block.sign.SignSide; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/* + * As soon as 1.18 support is dropped, the adapter system will be dropped too + */ +@Deprecated(forRemoval = true) +public class SignListener extends AbstractSignListener { + + protected final SignWrapper createFromSide(final Sign sign, final Side side) { + SignSide signSide = sign.getSide(side); + return createFromSide(sign, signSide, side); + } + + protected final SignWrapper createFromSide(final Sign sign, final SignSide signSide, Side side) { + BlockFace face = ((Directional) sign.getBlockData()).getFacing(); + if (side == Side.BACK) { + face = face.getOppositeFace(); + } + SignWrapper wrapper = new SignWrapper( + sign, + signSide::line, + signSide.lines(), + signSide::line, + face + ); + return wrapper; + } + + @Override + public SignWrapper[] getSignWrappers(Sign sign, boolean ignoreEmpty) { + Side[] sides = new Side[Side.values().length]; + List wrappers = new ArrayList<>(); + for (int i = 0; i < sides.length; i++) { + Side side = sides[i]; + SignSide signSide = sign.getSide(side); + SignWrapper wrapper = this.createFromSide(sign, signSide, side); + wrappers.add(wrapper); + } + if (ignoreEmpty) + wrappers.removeIf(SignWrapper::isEmpty); + return wrappers.toArray(new SignWrapper[wrappers.size()]); + } + + @Override + protected SignWrapper getSignWrapper(Sign sign, SignChangeEvent signChangeEvent) { + @NotNull Side side = signChangeEvent.getSide(); + BlockFace face = ((Directional) sign.getBlockData()).getFacing(); + if (side == Side.BACK) { + face = face.getOppositeFace(); + } + SignWrapper wrapper = new SignWrapper( + sign, + signChangeEvent::line, + signChangeEvent.lines(), + signChangeEvent::line, + face + ); + return wrapper; + } + + @Override + protected SignWrapper getSignWrapper(Sign sign, PlayerInteractEvent interactEvent) { + @NotNull SignSide side = sign.getTargetSide(interactEvent.getPlayer()); + return this.createFromSide(sign, side, sign.getInteractableSideFor(interactEvent.getPlayer())); + } + + @Override + public void processSignTranslation(Craft craft, boolean checkEventIsUpdated) { + Object2ObjectMap> signs = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { + @Override + public int hashCode(SignWrapper strings) { + return Arrays.hashCode(strings.rawLines()) * strings.facing().hashCode(); + } + + @Override + public boolean equals(SignWrapper a, SignWrapper b) { + return SignWrapper.areSignsEqual(a, b); + } + }); + Map signStates = new HashMap<>(); + + for (MovecraftLocation location : craft.getHitBox()) { + Block block = location.toBukkit(craft.getWorld()).getBlock(); + if(!Tag.SIGNS.isTagged(block.getType())){ + continue; + } + BlockState state = block.getState(); + if (state instanceof Sign) { + Sign sign = (Sign) state; + SignWrapper[] wrappersAtLoc = this.getSignWrappers(sign, true); + if (wrappersAtLoc == null || wrappersAtLoc.length == 0) { + continue; + } + for (SignWrapper wrapper : wrappersAtLoc) { + List values = signs.computeIfAbsent(wrapper, (w) -> new ArrayList<>()); + values.add(location); + } + signStates.put(location, wrappersAtLoc); + } + } + for(Map.Entry> entry : signs.entrySet()){ + final List components = new ArrayList<>(entry.getKey().lines()); + SignWrapper backingForEvent = new SignWrapper(null, components::get, components, components::set, entry.getKey().facing()); + SignTranslateEvent event = new SignTranslateEvent(craft, backingForEvent, entry.getValue()); + Bukkit.getServer().getPluginManager().callEvent(event); + // if(!event.isUpdated()){ + // continue; + // } + // TODO: This is implemented only to fix client caching + // ideally we wouldn't do the update and would instead fake it out to the player + for(MovecraftLocation location : entry.getValue()){ + Block block = location.toBukkit(craft.getWorld()).getBlock(); + BlockState state = block.getState(); + if (!(state instanceof Sign)) { + continue; + } + SignWrapper[] signsAtLoc = signStates.get(location); + if (signsAtLoc != null && signsAtLoc.length > 0) { + for (SignWrapper sw : signsAtLoc) { + // Important: Check if the wrapper faces the right way! + if (!sw.facing().equals(entry.getKey().facing())) { + continue; + } + if (!checkEventIsUpdated || event.isUpdated()) { + sw.copyContent(entry.getKey()); + } + } + try { + ((Sign)location.toBukkit(craft.getWorld()).getBlock()).update(false, false); + } catch(ClassCastException ex) { + // Ignore + } + } + } + } + } +}