Merge branch 'railswitch' into mc/1.20

# Conflicts:
#	settings.gradle.kts
This commit is contained in:
okx-code
2024-02-23 01:27:46 +00:00
15 changed files with 571 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# Editorconfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = crlf
insert_final_newline = true
continuation_indent_size = 8
[{*.xml, *.yml}]
indent_size = 2

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2019 okx-code
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,26 @@
# RailSwitch
## How to use
### Setup
1. Place a sign in the block above a detector rail. **It must be reinforced on the same group as the detector rail.**
2. The first line must be `[destination]` (case insensitive)
3. The second, third and fourth lines can be destination names.
4. When a player passes over the detector rail, it will only activate and emit redstone if a player set their destination as any of the three on the sign.
You may also use `[!destination]`, which will activate the rail if a player's destination is **not** on the signs.
### Usage
1. Type `/dest <destination>`
2. Go AFK
3. Arrive
You can also use spaces in a `/dest` command as an "or" operator, like `/dest <destination1> <destination2>`. In this command, RailSwitch will route players towards signs that contain either `<destination1>` or `<destination2>`.
Another way to think about it is that `/dest` takes multiple arguments. Each argument is a destination that will be checked as you pass a sign.
### Example Video
[![RailSwitch Demo Video](https://img.youtube.com/vi/GKku2fcB-wY/0.jpg)](https://www.youtube.com/watch?v=GKku2fcB-wY)

View File

@@ -0,0 +1,13 @@
plugins {
id("io.papermc.paperweight.userdev")
}
version = "2.0.0-SNAPSHOT"
dependencies {
paperDevBundle("1.18.2-R0.1-SNAPSHOT")
compileOnly(project(":plugins:civmodcore-paper"))
compileOnly(project(":plugins:namelayer-paper"))
compileOnly(project(":plugins:citadel-paper"))
}

View File

@@ -0,0 +1,39 @@
package sh.okx.railswitch;
import org.bukkit.event.Listener;
import sh.okx.railswitch.commands.DestinationCommand;
import sh.okx.railswitch.glue.CitadelGlue;
import sh.okx.railswitch.settings.SettingsManager;
import sh.okx.railswitch.switches.SwitchListener;
import vg.civcraft.mc.civmodcore.ACivMod;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
/**
* The Rail Switch plugin class
*/
public final class RailSwitchPlugin extends ACivMod implements Listener {
private CommandManager commandManager;
@Override
public void onEnable() {
super.onEnable();
SettingsManager.init(this);
registerListener(new CitadelGlue(this));
registerListener(new SwitchListener());
commandManager = new CommandManager(this);
commandManager.init();
commandManager.registerCommand(new DestinationCommand());
}
@Override
public void onDisable() {
SettingsManager.reset();
if (commandManager != null) {
commandManager.reset();
commandManager = null;
}
super.onDisable();
}
}

View File

@@ -0,0 +1,23 @@
package sh.okx.railswitch.commands;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Syntax;
import org.bukkit.entity.Player;
import sh.okx.railswitch.settings.SettingsManager;
/**
* Continued support for setting and resetting your destination via a command.
*/
public final class DestinationCommand extends BaseCommand {
@CommandAlias("dest|destination|setdestination|switch|setswitch|setsw")
@Description("Set your rail destination(s)")
@Syntax("[destination]")
public void onSetDestination(Player player, @Optional String destination) {
SettingsManager.setDestination(player, destination);
}
}

View File

@@ -0,0 +1,66 @@
package sh.okx.railswitch.glue;
import org.bukkit.block.Block;
import org.bukkit.plugin.Plugin;
import vg.civcraft.mc.citadel.Citadel;
import vg.civcraft.mc.citadel.ReinforcementManager;
import vg.civcraft.mc.citadel.model.Reinforcement;
import vg.civcraft.mc.civmodcore.utilities.DependencyGlue;
import vg.civcraft.mc.civmodcore.utilities.NullUtils;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
/**
* Glue for Citadel.
*/
public final class CitadelGlue extends DependencyGlue {
private ReinforcementManager manager;
public CitadelGlue(Plugin plugin) {
super(plugin, "Citadel");
}
public boolean isSafeToUse() {
if (!super.isDependencyEnabled()) {
return false;
}
if (this.manager == null) {
return false;
}
return true;
}
/**
* Do two blocks, representing the sign and the rail, have the same reinforcement group?
*
* @param sign The block representing the sign.
* @param rail The block representing the rail.
* @return Returns true both blocks share the same reinforcement group, or if both are un-reinforced.
*/
public boolean doSignAndRailHaveSameReinforcement(Block sign, Block rail) {
if (!isSafeToUse() || !WorldUtils.isValidBlock(sign) || !WorldUtils.isValidBlock(rail)) {
return false;
}
Reinforcement signReinforcement = this.manager.getReinforcement(sign);
Reinforcement railReinforcement = this.manager.getReinforcement(rail);
if (signReinforcement == null && railReinforcement == null) {
return true;
}
if (signReinforcement == null || railReinforcement == null) {
return false;
}
return NullUtils.equalsNotNull(
signReinforcement.getGroup(),
railReinforcement.getGroup());
}
@Override
protected void onDependencyEnabled() {
this.manager = Citadel.getInstance().getReinforcementManager();
}
@Override
protected void onDependencyDisabled() {
this.manager = null;
}
}

View File

@@ -0,0 +1,27 @@
package sh.okx.railswitch.settings;
import com.google.common.base.Strings;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import vg.civcraft.mc.civmodcore.players.settings.impl.StringSetting;
/**
* Setting representing a player's destination.
*/
public final class DestinationSetting extends StringSetting {
public DestinationSetting(JavaPlugin plugin) {
super(plugin, "", "Destination", "dest", new ItemStack(Material.MINECART),
"The destination(s) that will be used to route you at rail junctions.");
}
@Override
public String toText(String value) {
if (Strings.isNullOrEmpty(value)) {
return "<empty>";
}
return value;
}
}

View File

@@ -0,0 +1,18 @@
package sh.okx.railswitch.settings;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI;
import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection;
/**
* The menu for RailSwitch. This is what all RailSwitch settings will be registered to.
*/
public final class RailSwitchMenu extends MenuSection {
public RailSwitchMenu() {
super("RailSwitch", "Settings relating to RailSwitch", PlayerSettingAPI.getMainMenu(),
new ItemStack(Material.RAIL));
}
}

View File

@@ -0,0 +1,50 @@
package sh.okx.railswitch.settings;
import java.util.UUID;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection;
import vg.civcraft.mc.civmodcore.players.settings.impl.StringSetting;
/**
* ResetSetting, a setting that functions more like a GUI button than an actual setting.
*/
public final class ResetSetting extends StringSetting {
private final DestinationSetting destinationSetting;
public ResetSetting(JavaPlugin plugin, DestinationSetting destinationSetting) {
super(plugin, "", "Clear Destination", "dest", new ItemStack(Material.BARRIER), "Clears your destination.");
this.destinationSetting = destinationSetting;
}
@Override
public void handleMenuClick(Player player, MenuSection menu) {
resetPlayerDestination(player);
menu.showScreen(player);
}
/**
* Resets a player's destination value.
*
* @param player The player to reset the destination for.
*/
public void resetPlayerDestination(Player player) {
this.destinationSetting.setValue(player, "");
player.sendMessage(ChatColor.GREEN + "Your destination has been reset.");
}
@Override
public String getValue(UUID player) {
return this.destinationSetting.getValue(player);
}
@Override
public String toText(String value) {
return this.destinationSetting.toText(value);
}
}

View File

@@ -0,0 +1,88 @@
package sh.okx.railswitch.settings;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import sh.okx.railswitch.RailSwitchPlugin;
/**
* Manages the initialisation and registration of menu settings.
*/
public final class SettingsManager {
private static RailSwitchMenu menu;
private static DestinationSetting destSetting;
private static ResetSetting resetSetting;
/**
* Initialise the settings manager. This should only be called within RailSwitch onEnable().
*
* @param plugin The enabled RailSwitch plugin instance.
*/
public static void init(RailSwitchPlugin plugin) {
// Create the menu elements
menu = new RailSwitchMenu();
destSetting = new DestinationSetting(plugin);
resetSetting = new ResetSetting(plugin, destSetting);
// Register those elements
menu.registerToParentMenu();
menu.registerSetting(destSetting);
menu.registerSetting(resetSetting);
}
/**
* Gracefully resets the settings manager. This should only be called within RailSwitch onDisable().
*/
public static void reset() {
// TODO: Deregister and unload all the menu elements once PlayerSettingAPI becomes reload safe
menu = null;
destSetting = null;
resetSetting = null;
}
/**
* Sets a player's destination. This is for when the player settings don't themselves provide a way to do so.
*
* @param player The player to set the destination to.
* @param destination The destination to set.
*/
public static void setDestination(Player player, String destination) {
Preconditions.checkArgument(player != null);
if (Strings.isNullOrEmpty(destination)) {
if (resetSetting != null) {
resetSetting.resetPlayerDestination(player);
// Do not put a message here since the message is sent in the method above.
}
else {
player.sendMessage(ChatColor.RED + "Could not reset your destination.");
}
}
else {
if (destSetting != null) {
destSetting.setValue(player, destination);
player.sendMessage(ChatColor.GREEN + "Destination set to: " + destination);
}
else {
player.sendMessage(ChatColor.RED + "Could not set your destination.");
}
}
}
/**
* Gets a player's destination.
*
* @param player The player to get the destination for.
* @return Returns the player's destination, which will never be null.
*/
public static String getDestination(Player player) {
String value = destSetting.getValue(player);
if (value == null) {
return "";
}
return value;
}
}

View File

@@ -0,0 +1,129 @@
package sh.okx.railswitch.switches;
import com.google.common.base.Strings;
import java.util.Arrays;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockRedstoneEvent;
import sh.okx.railswitch.RailSwitchPlugin;
import sh.okx.railswitch.glue.CitadelGlue;
import sh.okx.railswitch.settings.SettingsManager;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
/**
* Switch listener that implements switch functionality.
*/
public class SwitchListener implements Listener {
public static final String WILDCARD = "*";
public static final CitadelGlue CITADEL_GLUE = new CitadelGlue(RailSwitchPlugin.getPlugin(RailSwitchPlugin.class));
/**
* Event handler for rail switches. Will determine if a switch exists at the target location, and if so will process
* it accordingly, allowing it to trigger or not trigger depending on the rider's set destination, the listed
* destinations on the switch, and the switch type.
*
* @param event The block redstone event to base the switch's existence on.
*/
@EventHandler
public void onSwitchTrigger(BlockRedstoneEvent event) {
Block block = event.getBlock();
// Block must be a detector rail being triggered
if (!WorldUtils.isValidBlock(block)
|| block.getType() != Material.DETECTOR_RAIL
|| event.getNewCurrent() != 15) {
return;
}
// Check that the block above the rail is a sign
Block above = block.getRelative(BlockFace.UP);
if (!Tag.SIGNS.isTagged(above.getType())
|| !(above.getState() instanceof Sign)) {
return;
}
// Check that the sign has a valid switch type
String[] lines = ((Sign) above.getState()).getLines();
SwitchType type = SwitchType.find(lines[0]);
if (type == null) {
return;
}
// Check that a player is triggering the switch
// NOTE: The event doesn't provide the information and so the next best thing is searching for a
// player who is nearby and riding a minecart.
Player player = null; {
double searchDistance = Double.MAX_VALUE;
for (Entity entity : block.getWorld().getNearbyEntities(block.getLocation(), 3, 3, 3)) {
if (!(entity instanceof Player)) {
continue;
}
Entity vehicle = entity.getVehicle();
// TODO: This should be abstracted into CivModCore
if (vehicle == null
|| vehicle.getType() != EntityType.MINECART
|| !(vehicle instanceof Minecart)) {
continue;
}
double distance = block.getLocation().distanceSquared(entity.getLocation());
if (distance < searchDistance) {
searchDistance = distance;
player = (Player) entity;
}
}
}
if (player == null) {
return;
}
// If Citadel is enabled, check that the sign and the rail are on the same group
if (CITADEL_GLUE.isSafeToUse()) {
if (!CITADEL_GLUE.doSignAndRailHaveSameReinforcement(above, block)) {
return;
}
}
// Determine whether a player has a destination that matches one of the destinations
// listed on the switch signs, or match if there's a wildcard.
boolean matched = false;
String setDest = SettingsManager.getDestination(player);
if (!Strings.isNullOrEmpty(setDest)) {
String[] playerDestinations = setDest.split(" ");
String[] switchDestinations = Arrays.copyOfRange(lines, 1, lines.length);
matcher:
for (String playerDestination : playerDestinations) {
if (Strings.isNullOrEmpty(playerDestination)) {
continue;
}
if (playerDestination.equals(WILDCARD)) {
matched = true;
break;
}
for (String switchDestination : switchDestinations) {
if (Strings.isNullOrEmpty(switchDestination)) {
continue;
}
if (switchDestination.equals(WILDCARD)
|| playerDestination.equalsIgnoreCase(switchDestination)) {
matched = true;
break matcher;
}
}
}
}
switch (type) {
case NORMAL:
event.setNewCurrent(matched ? 15 : 0);
break;
case INVERTED:
event.setNewCurrent(matched ? 0 : 15);
break;
}
}
}

View File

@@ -0,0 +1,43 @@
package sh.okx.railswitch.switches;
import org.apache.commons.lang3.StringUtils;
/**
* Switch type matcher, will match a type to a tag.
*/
public enum SwitchType {
NORMAL("[destination]"),
INVERTED("[!destination]");
private final String tag;
/**
* Defines a new switch type by its tag. Should there be a duplicate tag, the {@link SwitchType#find(String) find()}
* function will return the first value found.
*
* @param tag The tag to associate with this type.
*/
SwitchType(String tag) {
this.tag = tag;
}
/**
* Attempts to match a switch type to a switch tag.
*
* @param tag The tag to match with a type.
* @return Returns a match switch type, or null if none are found.
*/
public static SwitchType find(String tag) {
if (tag == null || tag.isEmpty()) {
return null;
}
for (SwitchType type : values()) {
if (StringUtils.equalsIgnoreCase(tag, type.tag)) {
return type;
}
}
return null;
}
}

View File

@@ -0,0 +1,10 @@
name: RailSwitch
version: ${version}
main: sh.okx.railswitch.RailSwitchPlugin
depend: [CivModCore]
softdepend: [Citadel, NameLayer]
api-version: 1.17
authors: [Okx, Protonull]
description: Easily make rail switches for automated destination selection
commands:
dest: {}

View File

@@ -28,3 +28,4 @@ include(":plugins:essenceglue-paper")
include(":plugins:hiddenore-paper")
include(":plugins:kirabukkitgateway-paper")
include(":plugins:namecolors-paper")
include(":plugins:railswitch-paper")