This commit is contained in:
parent
3318863cf8
commit
dde8700248
13 changed files with 468 additions and 3 deletions
|
@ -81,6 +81,9 @@ Issue messages that the player needs to read to keep playing, and that make an a
|
|||
|
||||
`/emergencyalerts` (`tweaks724.emergencyalerts`)
|
||||
|
||||
### Remote redstone
|
||||
Control redstone remotely
|
||||
|
||||
### Utility commands
|
||||
|
||||
- `/ping` - displays player ping \
|
||||
|
|
32
RETSTONE.md
Normal file
32
RETSTONE.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
## Remote redstone
|
||||
|
||||
See [retstone.py](retstone.py) for usage example
|
||||
|
||||
### Glossary
|
||||
repeaters - the blocks which allow to interact with redstone over internet \
|
||||
packet - a bunch of bytes sent over the internet, that do something with a single repeater
|
||||
|
||||
### How it works
|
||||
A packet is int / 4 bytes / 32 bits
|
||||
|
||||
Packet format:
|
||||
```
|
||||
[ 01 ] [ 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ] [ 29 30 31 32 ]
|
||||
action bits 2-28 of repeater id payload
|
||||
```
|
||||
|
||||
- action: `1` to write, `0` to read
|
||||
- payload: power level if writing
|
||||
|
||||
If writing, no response \
|
||||
If reading, response is the power level, or -1 if no repeater with that id (subject to change)
|
||||
|
||||
Reading powers the block down of course \
|
||||
If the block was powered, you should power it down (or read), wait some, and then read again
|
||||
|
||||
### Retstone
|
||||
|
||||
**Network** translates to **reto** in Esperanto \
|
||||
So retsomething means networked something (posto - mail, retposto - email, ejo - place (site), retejo - website, etc.) \
|
||||
And sometimes we use network instead of internet, same is in that language \
|
||||
Hence retstone
|
32
retstone.py
Normal file
32
retstone.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2024 Minecon724
|
||||
# Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
# in the project root for the full license text.
|
||||
|
||||
import socket, struct
|
||||
|
||||
ENDPOINT = ("127.0.0.1", 57931)
|
||||
|
||||
def get_power(repeater_id: int) -> int | None:
|
||||
message = repeater_id & 0x7FFFFFF0
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.sendto(message.to_bytes(4), ENDPOINT)
|
||||
|
||||
return struct.unpack(">b", sock.recvfrom(1)[0])[0]
|
||||
|
||||
def set_power(repeater_id: int, power: int):
|
||||
message = repeater_id & 0x7FFFFFF0
|
||||
message |= 0x80000000
|
||||
message |= power & 0xF
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.sendto(message.to_bytes(4), ENDPOINT)
|
||||
|
||||
repeater_id = 235459920
|
||||
|
||||
print("Reading from repeater")
|
||||
power = get_power(repeater_id)
|
||||
print("Read power:", power)
|
||||
|
||||
print("Powering repeater with power 10")
|
||||
set_power(repeater_id, 10)
|
|
@ -49,7 +49,10 @@ public record TweaksConfig(
|
|||
|
||||
boolean authEnabled,
|
||||
boolean authForce,
|
||||
String authDomain
|
||||
String authDomain,
|
||||
|
||||
boolean redstoneEnabled,
|
||||
String redstoneListen
|
||||
) {
|
||||
public static final int CONFIG_VERSION = 1;
|
||||
private static TweaksConfig config;
|
||||
|
@ -113,6 +116,9 @@ public record TweaksConfig(
|
|||
boolean authForce = config.getBoolean("auth.force");
|
||||
String authHostname = config.getString("auth.domain");
|
||||
|
||||
boolean redstoneEnabled = config.getBoolean("retstone.enabled");
|
||||
String redstoneListen = config.getString("retstone.listen");
|
||||
|
||||
TweaksConfig.config = new TweaksConfig(
|
||||
metrics,
|
||||
worldborderExpand, worldborderHide,
|
||||
|
@ -125,7 +131,8 @@ public record TweaksConfig(
|
|||
updaterEnabled,
|
||||
hardcoreEnabled, hardcoreChance,
|
||||
sleepEnabled, sleepInstant,
|
||||
authEnabled, authForce, authHostname
|
||||
authEnabled, authForce, authHostname,
|
||||
redstoneEnabled, redstoneListen
|
||||
);
|
||||
|
||||
return TweaksConfig.config;
|
||||
|
|
|
@ -20,6 +20,7 @@ import eu.m724.tweaks.ping.PingChecker;
|
|||
import eu.m724.tweaks.ping.PingCommands;
|
||||
import eu.m724.tweaks.pomodoro.PomodoroCommands;
|
||||
import eu.m724.tweaks.pomodoro.PomodoroManager;
|
||||
import eu.m724.tweaks.redstone.RedstoneManager;
|
||||
import eu.m724.tweaks.sleep.SleepManager;
|
||||
import eu.m724.tweaks.updater.UpdaterCommands;
|
||||
import eu.m724.tweaks.updater.UpdaterManager;
|
||||
|
@ -112,6 +113,10 @@ public class TweaksPlugin extends MStatsPlugin {
|
|||
|
||||
this.getServer().getPluginManager().registerEvents(new FullListener(), this);
|
||||
|
||||
if (config.redstoneEnabled()) {
|
||||
new RedstoneManager(this).init(getCommand("retstone"));
|
||||
}
|
||||
|
||||
if (config.metrics())
|
||||
mStats(1);
|
||||
|
||||
|
|
52
src/main/java/eu/m724/tweaks/redstone/RedstoneCommands.java
Normal file
52
src/main/java/eu/m724/tweaks/redstone/RedstoneCommands.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.redstone;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class RedstoneCommands implements CommandExecutor {
|
||||
private final RedstoneRepeaters redstoneRepeaters;
|
||||
|
||||
public RedstoneCommands(RedstoneRepeaters redstoneRepeaters) {
|
||||
this.redstoneRepeaters = redstoneRepeaters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (args.length > 0) {
|
||||
if (args[0].equals("give")) {
|
||||
Player player = null;
|
||||
|
||||
if (args.length > 1) {
|
||||
player = Bukkit.getPlayerExact(args[1]);
|
||||
if (player == null) {
|
||||
sender.sendMessage("No player named " + args[1]);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (sender instanceof Player) {
|
||||
player = (Player) sender;
|
||||
} else {
|
||||
sender.sendMessage("Specify a player to give to, or be a player");
|
||||
}
|
||||
}
|
||||
|
||||
var itemStack = redstoneRepeaters.give();
|
||||
player.getInventory().addItem(itemStack);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Argument needed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
54
src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java
Normal file
54
src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.redstone;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.block.BlockRedstoneEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
public class RedstoneListener implements Listener {
|
||||
private final RedstoneRepeaters redstoneRepeaters;
|
||||
|
||||
public RedstoneListener(RedstoneRepeaters redstoneRepeaters) {
|
||||
this.redstoneRepeaters = redstoneRepeaters;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlace(BlockPlaceEvent event) {
|
||||
if (!redstoneRepeaters.isRepeater(event.getItemInHand())) return;
|
||||
|
||||
var block = event.getBlockPlaced();
|
||||
var id = redstoneRepeaters.onPlace(block);
|
||||
|
||||
System.out.println("repeate place " + id);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBreak(BlockBreakEvent event) {
|
||||
var id = redstoneRepeaters.getId(event.getBlock());
|
||||
if (id == Integer.MIN_VALUE) return;
|
||||
|
||||
redstoneRepeaters.onBreak(id);
|
||||
|
||||
System.out.println("repeate brek " + id);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void a(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||
if (event.getClickedBlock() == null) return;
|
||||
|
||||
var id = redstoneRepeaters.getId(event.getClickedBlock());
|
||||
if (id == Integer.MIN_VALUE) return;
|
||||
|
||||
event.getPlayer().sendMessage("Repeater ID: " + id);
|
||||
}
|
||||
}
|
101
src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java
Normal file
101
src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.redstone;
|
||||
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class RedstoneManager {
|
||||
private final Plugin plugin;
|
||||
private final RedstoneRepeaters redstoneRepeaters;
|
||||
|
||||
private DatagramSocket socket;
|
||||
private RedstoneStateUpdateRunnable runnable;
|
||||
|
||||
public RedstoneManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.redstoneRepeaters = new RedstoneRepeaters(plugin);
|
||||
}
|
||||
|
||||
public void init(PluginCommand command) {
|
||||
plugin.getServer().getPluginManager().registerEvents(new RedstoneListener(redstoneRepeaters), plugin);
|
||||
command.setExecutor(new RedstoneCommands(redstoneRepeaters));
|
||||
|
||||
this.runnable = new RedstoneStateUpdateRunnable(redstoneRepeaters);
|
||||
this.runnable.runTaskTimer(plugin, 0, 20); // TODO configurable
|
||||
|
||||
var listenAddress = TweaksConfig.getConfig().redstoneListen().split(":");
|
||||
InetSocketAddress bindAddress;
|
||||
if (listenAddress.length == 1) {
|
||||
bindAddress = new InetSocketAddress(Integer.parseInt(listenAddress[0]));
|
||||
} else {
|
||||
bindAddress = new InetSocketAddress(listenAddress[0], Integer.parseInt(listenAddress[1]));
|
||||
}
|
||||
|
||||
try {
|
||||
initSocket(bindAddress);
|
||||
} catch (SocketException e) {
|
||||
throw new RuntimeException("Starting socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initSocket(SocketAddress bindAddress) throws SocketException {
|
||||
socket = new DatagramSocket(bindAddress);
|
||||
|
||||
Executors.newSingleThreadExecutor().execute(() -> {
|
||||
byte[] buf = new byte[4];
|
||||
|
||||
while (!socket.isClosed()) {
|
||||
DatagramPacket packet
|
||||
= new DatagramPacket(buf, buf.length);
|
||||
try {
|
||||
socket.receive(packet);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error reading packet: " + e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean write = (buf[0] >> 7 & 1) == 1;
|
||||
byte state = (byte) (buf[3] & 0xF);
|
||||
int repeaterId = ((buf[0] & 0x7F) << 24) | ((buf[1] & 0xFF) << 16) | ((buf[2] & 0xFF) << 8) | (buf[3] & 0xF0);
|
||||
|
||||
if (write) {
|
||||
enqueueUpdate(repeaterId, state);
|
||||
} else {
|
||||
var newPacket = new DatagramPacket(new byte[1], 1, packet.getSocketAddress());
|
||||
|
||||
enqueueRetrieve(repeaterId, value -> {
|
||||
System.out.println("retieved state " + value);
|
||||
newPacket.setData(new byte[] { value });
|
||||
|
||||
try {
|
||||
socket.send(newPacket);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Sending response to get repeater value", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void enqueueUpdate(int repeaterId, byte state) {
|
||||
System.out.println("Update enqueud " + repeaterId + " " + state);
|
||||
runnable.enqueueUpdate(repeaterId, state);
|
||||
}
|
||||
|
||||
private void enqueueRetrieve(int repeaterId, Consumer<Byte> consumer) {
|
||||
System.out.println("retieve enqueud " + repeaterId);
|
||||
runnable.enqueueRetrieve(repeaterId, consumer);
|
||||
}
|
||||
}
|
126
src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java
Normal file
126
src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.redstone;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.metadata.FixedMetadataValue;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class RedstoneRepeaters {
|
||||
private final Plugin plugin;
|
||||
private final NamespacedKey repeaterKey;
|
||||
|
||||
private final Map<Integer, Location> repeatersById = new HashMap<>();
|
||||
private final Map<Location, Integer> repeatersByLocation = new HashMap<>();
|
||||
|
||||
public RedstoneRepeaters(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.repeaterKey = new NamespacedKey(plugin, "repeater");
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public boolean isRepeater(ItemStack itemStack) {
|
||||
var meta = itemStack.getItemMeta();
|
||||
if (meta == null) return false;
|
||||
var value = meta.getPersistentDataContainer().get(repeaterKey, PersistentDataType.BOOLEAN);
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public int getId(Block block) {
|
||||
return repeatersByLocation.getOrDefault(block.getLocation(), Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public ItemStack give() {
|
||||
var itemStack = new ItemStack(Material.RED_STAINED_GLASS);
|
||||
var meta = itemStack.getItemMeta();
|
||||
|
||||
meta.setItemName(Language.getString("retstoneBlockItem"));
|
||||
meta.getPersistentDataContainer().set(repeaterKey, PersistentDataType.BOOLEAN, true);
|
||||
meta.setEnchantmentGlintOverride(true);
|
||||
|
||||
itemStack.setItemMeta(meta);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
// TODO save in those
|
||||
|
||||
int onPlace(Block block) {
|
||||
var repeaterId = ThreadLocalRandom.current().nextInt() & 0x7FFFFFF0;
|
||||
|
||||
repeatersById.put(repeaterId, block.getLocation());
|
||||
repeatersByLocation.put(block.getLocation(), repeaterId);
|
||||
block.setMetadata("rid", new FixedMetadataValue(plugin, repeaterId));
|
||||
|
||||
return repeaterId;
|
||||
}
|
||||
|
||||
void onBreak(int repeaterId) {
|
||||
delete(repeaterId);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
Block getBlock(int repeaterId) {
|
||||
var location = repeatersById.get(repeaterId);
|
||||
if (location == null) return null;
|
||||
|
||||
var storedId = location.getBlock().getMetadata("rid").getFirst().asInt();
|
||||
if (storedId != repeaterId) {
|
||||
System.out.println("retrieved but not exitt");
|
||||
delete(repeaterId);
|
||||
return null;
|
||||
}
|
||||
|
||||
System.out.println("retrieved exist " + repeaterId);
|
||||
return location.getBlock();
|
||||
}
|
||||
|
||||
void delete(int repeaterId) {
|
||||
var l = repeatersById.remove(repeaterId);
|
||||
if (l == null) return;
|
||||
repeatersByLocation.remove(l);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
byte getPower(int repeaterId) {
|
||||
var block = getBlock(repeaterId);
|
||||
if (block == null) return -1;
|
||||
|
||||
block.setType(Material.RED_STAINED_GLASS);
|
||||
|
||||
block.getWorld().spawnParticle(Particle.LAVA, block.getLocation().add(0.5, 0.5, 0.5), 3);
|
||||
|
||||
return (byte) block.getBlockPower();
|
||||
}
|
||||
|
||||
void setPower(int repeaterId, byte power) {
|
||||
var block = getBlock(repeaterId);
|
||||
if (block == null) return;
|
||||
|
||||
if (power == 0)
|
||||
block.setType(Material.RED_STAINED_GLASS);
|
||||
else
|
||||
block.setType(Material.REDSTONE_BLOCK);
|
||||
|
||||
block.getWorld().spawnParticle(Particle.LAVA, block.getLocation().add(0.5, 0.5, 0.5), 3);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.redstone;
|
||||
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class RedstoneStateUpdateRunnable extends BukkitRunnable {
|
||||
private Map<Integer, Byte> updateQueue = new HashMap<>();
|
||||
private Map<Integer, Consumer<Byte>> retrieveQueue = new HashMap<>();
|
||||
|
||||
private final RedstoneRepeaters redstoneRepeaters;
|
||||
|
||||
RedstoneStateUpdateRunnable(RedstoneRepeaters redstoneRepeaters) {
|
||||
this.redstoneRepeaters = redstoneRepeaters;
|
||||
}
|
||||
|
||||
void enqueueUpdate(int repeaterId, byte power) {
|
||||
updateQueue.put(repeaterId, power);
|
||||
}
|
||||
|
||||
void enqueueRetrieve(int repeaterId, Consumer<Byte> consumer) {
|
||||
retrieveQueue.put(repeaterId, consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
var updateQueue = this.updateQueue;
|
||||
this.updateQueue = new HashMap<>();
|
||||
|
||||
var retrieveQueue = this.retrieveQueue;
|
||||
this.retrieveQueue = new HashMap<>();
|
||||
|
||||
updateQueue.forEach((key, value) -> redstoneRepeaters.setPower(key, value));
|
||||
retrieveQueue.forEach((key, value) -> value.accept(redstoneRepeaters.getPower(key)));
|
||||
}
|
||||
}
|
|
@ -103,6 +103,12 @@ auth:
|
|||
# The domain of the server. Doesn't do anything other than showing in /tauth new
|
||||
domain: "replace.me"
|
||||
|
||||
# Adds blocks which allow to interact with redstone over internet
|
||||
retstone:
|
||||
enabled: true
|
||||
# This takes host:port, listens on UDP
|
||||
listen: 127.0.0.1:57931
|
||||
|
||||
# Finally, thank you for downloading Tweaks724, I hope you enjoy!
|
||||
|
||||
# Don't modify unless told to
|
||||
|
|
|
@ -34,6 +34,7 @@ commands:
|
|||
emergencyalert:
|
||||
description: Send emergency alert
|
||||
permission: tweaks724.emergencyalert
|
||||
retstone:
|
||||
|
||||
permissions:
|
||||
tweaks724:
|
||||
|
|
|
@ -24,3 +24,5 @@ chatAlreadyHere = You're already in this room
|
|||
authKickWrongKey = You're connecting to the wrong server address. You must connect to the one you're registered to.
|
||||
# If force is enabled and player is not registered. Changing this reveals you're using this plugin
|
||||
authKickUnregistered = You are not whitelisted on this server!
|
||||
|
||||
retstoneBlockItem = Online redstone block
|
Loading…
Reference in a new issue