Compare commits
32 commits
tweaks-0.1
...
master
Author | SHA1 | Date | |
---|---|---|---|
4f752bef61 | |||
41583939ac | |||
22bb2d16d2 | |||
![]() |
598902ef33 | ||
![]() |
7cd334f4a2 | ||
![]() |
b421b48e51 | ||
![]() |
684e31bf07 | ||
![]() |
cf654fcb42 | ||
![]() |
0c4690e2e7 | ||
![]() |
05b9ca4cb8 | ||
![]() |
316d479fd8 | ||
![]() |
6ff6ec9d6b | ||
![]() |
541f095075 | ||
![]() |
1fafc90e04 | ||
![]() |
ad1a699d38 | ||
![]() |
7de46b879b | ||
![]() |
cbe9f77cca | ||
![]() |
c096674a15 | ||
![]() |
232e0bfa9a | ||
![]() |
9345efe1d4 | ||
![]() |
2761ed8757 | ||
![]() |
2a65e9dbcb | ||
![]() |
29f79a8771 | ||
![]() |
7b65be86ad | ||
![]() |
2788829967 | ||
![]() |
cbfbdf8ce3 | ||
![]() |
1ac32a093f | ||
![]() |
15c97ee256 | ||
![]() |
7ed9a702ca | ||
![]() |
bcd6827c29 | ||
![]() |
2890f00acd | ||
![]() |
f43b17078e |
85 changed files with 1678 additions and 664 deletions
|
@ -2,10 +2,10 @@ on: [push]
|
|||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
container: debian:sid
|
||||
container: eclipse-temurin:21-alpine
|
||||
steps:
|
||||
- name: Install JDK and other deps
|
||||
run: apt update && apt install --no-install-recommends -y openjdk-21-jdk-headless maven git nodejs curl zstd
|
||||
- name: Install build dependencies
|
||||
run: apk add nodejs curl tar zstd
|
||||
|
||||
- name: Checkout
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
@ -14,19 +14,18 @@ jobs:
|
|||
run: ./tools/download_nms.sh ~
|
||||
|
||||
|
||||
|
||||
- name: Build for 1.21.1
|
||||
run: mvn package -Dproject.minecraft.version=1.21.1 -Dproject.nms.version=v1_21_R1
|
||||
- name: Build for 1.21.4
|
||||
run: ./mvnw package -Dproject.minecraft.version=1.21.4 -Dproject.craftbukkit.version=v1_21_R3
|
||||
|
||||
- name: Build for 1.21.3
|
||||
run: mvn package -Dproject.minecraft.version=1.21.3 -Dproject.nms.version=v1_21_R2
|
||||
run: ./mvnw package -Dproject.minecraft.version=1.21.3 -Dproject.craftbukkit.version=v1_21_R2
|
||||
|
||||
- name: Build for 1.21.4
|
||||
run: mvn package -Dproject.minecraft.version=1.21.4 -Dproject.nms.version=v1_21_R3
|
||||
- name: Build for 1.21.1
|
||||
run: ./mvnw package -Dproject.minecraft.version=1.21.1 -Dproject.craftbukkit.version=v1_21_R1
|
||||
|
||||
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: https://github.com/actions/upload-artifact@v3
|
||||
with:
|
||||
path: target
|
||||
path: target/tweaks-*.jar
|
12
README.md
12
README.md
|
@ -14,7 +14,7 @@ Stuff no<sub><sup>t many</sup></sub> other plugins do.
|
|||
Dependencies:
|
||||
- **1.21.1 and newer**
|
||||
- [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/)
|
||||
- To use modules marked <sup><sub>N</sub></sup>, you must use a JAR made precisely for your server version.
|
||||
- To use modules marked <sup><sub>N</sub></sup>, you must use a JAR [made for the exact server version.](/Minecon724/tweaks724/src/branch/master/docs/BUILDING.md)
|
||||
|
||||
# Features
|
||||
|
||||
|
@ -98,6 +98,16 @@ Quickly kills (terminates) the server on trigger, via command or HTTP request.
|
|||
### Swing through grass
|
||||
Self-explanatory
|
||||
|
||||
### Durability alert
|
||||
Self-explanatory too.
|
||||
|
||||
`/durabilityalert` (`tweaks724.durabilityalert`)
|
||||
|
||||
### Word coords
|
||||
Convert coordinates to easier to remember words
|
||||
|
||||
`/wordcoords` (`tweaks724.tauth`)
|
||||
|
||||
### Utility commands
|
||||
|
||||
- `/ping` - displays player ping <sup><sub>P</sub></sup> \
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
1. Download BuildTools, move it into an empty directory and open terminal
|
||||
2. Download Minecraft sources:
|
||||
```
|
||||
java -jar BuildTools.jar --rev 1.21.4 --remapped
|
||||
```
|
||||
First, download NMS. There are two ways:
|
||||
|
||||
3. Clone this repository:
|
||||
- Use `tools/download_nms.sh`
|
||||
- Download BuildTools, move it into an empty directory and run:
|
||||
```
|
||||
java -jar BuildTools.jar --rev 1.21.4 --remapped
|
||||
```
|
||||
You must run this for every version you want to build for.
|
||||
|
||||
|
||||
|
||||
Then build the plugin:
|
||||
|
||||
1. Clone this repository:
|
||||
```
|
||||
git clone https://git.m724.eu/Minecon724/tweaks724
|
||||
cd tweaks724
|
||||
|
@ -13,13 +20,13 @@
|
|||
```
|
||||
git checkout tags/tweaks-0.1.12
|
||||
```
|
||||
4. To compile for native version:
|
||||
2. For the "native" version:
|
||||
```
|
||||
./mvnw package
|
||||
```
|
||||
To compile for another version:
|
||||
For another compatible version:
|
||||
```
|
||||
./mvnw package -Dproject.craftbukkit.version=v1_21_R3 -Dproject.minecraft.version=1.21.4
|
||||
```
|
||||
|
||||
5. Look for `tweaks-0.1.12+1.21.4.jar` in `target/`
|
||||
Look for `tweaks-0.1.12+1.21.4.jar` in `target/`
|
11
pom.xml
11
pom.xml
|
@ -10,7 +10,7 @@
|
|||
|
||||
<groupId>eu.m724</groupId>
|
||||
<artifactId>tweaks</artifactId>
|
||||
<version>0.1.12</version>
|
||||
<version>0.1.15-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
|
@ -80,10 +80,8 @@
|
|||
<id>remap-obf</id>
|
||||
<configuration>
|
||||
<srgIn>org.spigotmc:minecraft-server:${project.spigot.version}:txt:maps-mojang</srgIn>
|
||||
<reverse>true</reverse>
|
||||
<remappedDependencies>org.spigotmc:spigot:${project.spigot.version}:jar:remapped-mojang</remappedDependencies>
|
||||
<remappedArtifactAttached>true</remappedArtifactAttached>
|
||||
<remappedClassifierName>remapped-obf-temp-dont-use</remappedClassifierName>
|
||||
<reverse>true</reverse>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
|
@ -93,7 +91,6 @@
|
|||
</goals>
|
||||
<id>remap-spigot</id>
|
||||
<configuration>
|
||||
<inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf-temp-dont-use.jar</inputFile>
|
||||
<srgIn>org.spigotmc:minecraft-server:${project.spigot.version}:csrg:maps-spigot</srgIn>
|
||||
<remappedDependencies>org.spigotmc:spigot:${project.spigot.version}:jar:remapped-obf</remappedDependencies>
|
||||
</configuration>
|
||||
|
@ -150,7 +147,7 @@
|
|||
<dependency>
|
||||
<groupId>eu.m724</groupId>
|
||||
<artifactId>mstats-spigot</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<version>0.1.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -171,6 +168,6 @@
|
|||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@git.m724.eu:Minecon724/tweaks724.git</developerConnection>
|
||||
<tag>tweaks-0.1.12</tag>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
</project>
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
package eu.m724.tweaks;
|
||||
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record TweaksConfig(
|
||||
boolean metrics,
|
||||
boolean debug,
|
||||
String locale,
|
||||
|
||||
boolean worldborderExpand,
|
||||
boolean worldborderHide,
|
||||
|
||||
boolean brandEnabled,
|
||||
String brandText,
|
||||
boolean brandShowPing,
|
||||
boolean brandShowMspt,
|
||||
|
||||
boolean doorDoubleOpen,
|
||||
boolean doorKnocking,
|
||||
|
||||
boolean motdEnabled,
|
||||
String motdSet,
|
||||
|
||||
boolean chatEnabled,
|
||||
boolean chatLocalEvents,
|
||||
String chatDefaultName,
|
||||
int chatRadius,
|
||||
|
||||
boolean compassEnabled,
|
||||
int compassWidth,
|
||||
int compassPrecision,
|
||||
|
||||
boolean pomodoroEnabled,
|
||||
boolean pomodoroForce,
|
||||
|
||||
boolean updaterEnabled,
|
||||
|
||||
boolean hardcoreEnabled,
|
||||
double hardcoreChance,
|
||||
|
||||
boolean sleepEnabled,
|
||||
boolean sleepInstant,
|
||||
double sleepHeal,
|
||||
|
||||
boolean authEnabled,
|
||||
boolean authForce,
|
||||
String authDomain,
|
||||
|
||||
boolean redstoneEnabled,
|
||||
String redstoneListen,
|
||||
|
||||
Map<String, Object> knockbackModifiers,
|
||||
|
||||
boolean killswitchEnabled,
|
||||
String killswitchListen,
|
||||
|
||||
boolean swingEnabled
|
||||
) {
|
||||
public static final int CONFIG_VERSION = 2;
|
||||
private static TweaksConfig config;
|
||||
|
||||
public static TweaksConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public static TweaksConfig load(Plugin plugin) {
|
||||
plugin.saveDefaultConfig();
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
|
||||
int configVersion = config.getInt("magic number don't modify this", 0);
|
||||
RuntimeException exception = new RuntimeException("Config version is %d, expected %d".formatted(configVersion, CONFIG_VERSION));
|
||||
|
||||
if (configVersion == 0) {
|
||||
throw exception;
|
||||
} else if (configVersion < CONFIG_VERSION) {
|
||||
throw new RuntimeException("Please follow update instructions https://www.spigotmc.org/resources/tweaks724.121057/updates", exception);
|
||||
} else if (configVersion > CONFIG_VERSION) {
|
||||
throw new RuntimeException("Did you downgrade the plugin? Remove config.yml and let the plugin re-create it", exception);
|
||||
}
|
||||
|
||||
boolean metrics = config.getBoolean("metrics");
|
||||
boolean debug = config.getBoolean("debug", false);
|
||||
String locale = config.getString("locale", "US");
|
||||
|
||||
boolean worldborderExpand = config.getBoolean("worldborder.expand");
|
||||
boolean worldborderHide = config.getBoolean("worldborder.hide");
|
||||
|
||||
boolean brandEnabled = config.getBoolean("brand.enabled");
|
||||
String brandText = config.getString("brand.text");
|
||||
boolean brandShowPing = config.getBoolean("brand.showPing");
|
||||
boolean brandShowMspt = config.getBoolean("brand.showMspt");
|
||||
|
||||
boolean doorDoubleOpen = config.getBoolean("doors.doubleOpen");
|
||||
boolean doorKnocking = config.getBoolean("doors.knocking");
|
||||
|
||||
String motdSet = config.getString("motd.set");
|
||||
boolean motdEnabled = !(motdSet.equals("false") || motdSet.isBlank());
|
||||
|
||||
boolean chatEnabled = config.getBoolean("chat.enabled");
|
||||
boolean chatLocalEvents = config.getBoolean("chat.localEvents");
|
||||
String chatDefaultName = config.getString("chat.defaultName");
|
||||
int chatRadius = config.getInt("chat.radius");
|
||||
|
||||
boolean compassEnabled = config.getBoolean("compass.enabled");
|
||||
int compassWidth = config.getInt("compass.width");
|
||||
int compassPrecision = config.getInt("compass.precision");
|
||||
|
||||
boolean pomodoroEnabled = config.getBoolean("pomodoro.enabled");
|
||||
boolean pomodoroForce = config.getBoolean("pomodoro.force");
|
||||
|
||||
boolean updaterEnabled = config.getBoolean("updater.enabled");
|
||||
|
||||
boolean hardcoreEnabled = config.getBoolean("hardcore.enabled");
|
||||
double hardcoreChance = config.getDouble("hardcore.chance");
|
||||
|
||||
boolean sleepEnabled = config.getBoolean("sleep.enabled");
|
||||
boolean sleepInstant = config.getBoolean("sleep.instant");
|
||||
double sleepHeal = config.getDouble("sleep.heal");
|
||||
|
||||
boolean authEnabled = config.getBoolean("auth.enabled");
|
||||
boolean authForce = config.getBoolean("auth.force");
|
||||
String authHostname = config.getString("auth.domain");
|
||||
|
||||
boolean redstoneEnabled = config.getBoolean("retstone.enabled");
|
||||
String redstoneListen = config.getString("retstone.listen");
|
||||
|
||||
// this is processed when initing
|
||||
Map<String, Object> knockbackModifiers = config.getConfigurationSection("knockback").getValues(false);
|
||||
|
||||
boolean killswitchEnabled = config.getBoolean("killswitch.enabled");
|
||||
String killswitchListen = config.getString("killswitch.listen");
|
||||
|
||||
boolean swingEnabled = config.getBoolean("swing.enabled");
|
||||
|
||||
TweaksConfig.config = new TweaksConfig(
|
||||
metrics, debug, locale,
|
||||
worldborderExpand, worldborderHide,
|
||||
brandEnabled, brandText, brandShowPing, brandShowMspt,
|
||||
doorDoubleOpen, doorKnocking,
|
||||
motdEnabled, motdSet,
|
||||
chatEnabled, chatLocalEvents, chatDefaultName, chatRadius,
|
||||
compassEnabled, compassWidth, compassPrecision,
|
||||
pomodoroEnabled, pomodoroForce,
|
||||
updaterEnabled,
|
||||
hardcoreEnabled, hardcoreChance,
|
||||
sleepEnabled, sleepInstant, sleepHeal,
|
||||
authEnabled, authForce, authHostname,
|
||||
redstoneEnabled, redstoneListen,
|
||||
knockbackModifiers,
|
||||
killswitchEnabled, killswitchListen,
|
||||
swingEnabled
|
||||
);
|
||||
|
||||
return TweaksConfig.config;
|
||||
}
|
||||
}
|
|
@ -7,25 +7,29 @@
|
|||
package eu.m724.tweaks;
|
||||
|
||||
import eu.m724.mstats.MStatsPlugin;
|
||||
import eu.m724.tweaks.alert.AlertModule;
|
||||
import eu.m724.tweaks.auth.AuthModule;
|
||||
import eu.m724.tweaks.chat.ChatModule;
|
||||
import eu.m724.tweaks.door.DoorKnockModule;
|
||||
import eu.m724.tweaks.door.DoorOpenModule;
|
||||
import eu.m724.tweaks.full.FullModule;
|
||||
import eu.m724.tweaks.hardcore.HardcoreModule;
|
||||
import eu.m724.tweaks.killswitch.KillswitchModule;
|
||||
import eu.m724.tweaks.knockback.KnockbackModule;
|
||||
import eu.m724.tweaks.motd.MotdModule;
|
||||
import eu.m724.tweaks.ping.F3NameListener;
|
||||
import eu.m724.tweaks.ping.PingChecker;
|
||||
import eu.m724.tweaks.pomodoro.PomodoroModule;
|
||||
import eu.m724.tweaks.redstone.RedstoneModule;
|
||||
import eu.m724.tweaks.sleep.SleepModule;
|
||||
import eu.m724.tweaks.swing.SwingModule;
|
||||
import eu.m724.tweaks.updater.UpdaterModule;
|
||||
import eu.m724.tweaks.worldborder.WorldBorderExpandModule;
|
||||
import eu.m724.tweaks.worldborder.WorldBorderHideModule;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import eu.m724.tweaks.module.alert.AlertModule;
|
||||
import eu.m724.tweaks.module.auth.AuthModule;
|
||||
import eu.m724.tweaks.module.chat.ChatModule;
|
||||
import eu.m724.tweaks.module.door.DoorKnockModule;
|
||||
import eu.m724.tweaks.module.door.DoorOpenModule;
|
||||
import eu.m724.tweaks.module.durability.DurabilityModule;
|
||||
import eu.m724.tweaks.module.full.FullModule;
|
||||
import eu.m724.tweaks.module.hardcore.HardcoreModule;
|
||||
import eu.m724.tweaks.module.killswitch.KillswitchModule;
|
||||
import eu.m724.tweaks.module.knockback.KnockbackModule;
|
||||
import eu.m724.tweaks.module.motd.MotdModule;
|
||||
import eu.m724.tweaks.module.ping.F3NameListener;
|
||||
import eu.m724.tweaks.module.ping.PingChecker;
|
||||
import eu.m724.tweaks.module.pomodoro.PomodoroModule;
|
||||
import eu.m724.tweaks.module.redstone.RedstoneModule;
|
||||
import eu.m724.tweaks.module.sleep.SleepModule;
|
||||
import eu.m724.tweaks.module.swing.SwingModule;
|
||||
import eu.m724.tweaks.module.updater.UpdaterModule;
|
||||
import eu.m724.tweaks.module.wordcoords.WordCoordsModule;
|
||||
import eu.m724.tweaks.module.worldborder.WorldBorderExpandModule;
|
||||
import eu.m724.tweaks.module.worldborder.WorldBorderHideModule;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
|
@ -45,7 +49,12 @@ public class TweaksPlugin extends MStatsPlugin {
|
|||
return;
|
||||
}
|
||||
|
||||
TweaksConfig config = TweaksConfig.load(this);
|
||||
TweaksConfig config;
|
||||
try {
|
||||
config = TweaksConfig.load(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Exception loading config", e);
|
||||
}
|
||||
|
||||
getLogger().setLevel(config.debug() ? Level.FINEST : Level.INFO);
|
||||
DebugLogger.logger = getLogger();
|
||||
|
@ -83,7 +92,6 @@ public class TweaksPlugin extends MStatsPlugin {
|
|||
|
||||
if (config.worldborderExpand()) {
|
||||
TweaksModule.init(WorldBorderExpandModule.class);
|
||||
|
||||
}
|
||||
|
||||
if (config.chatEnabled()) {
|
||||
|
@ -148,6 +156,10 @@ public class TweaksPlugin extends MStatsPlugin {
|
|||
TweaksModule.init(SwingModule.class);
|
||||
}
|
||||
|
||||
TweaksModule.init(DurabilityModule.class);
|
||||
|
||||
TweaksModule.init(WordCoordsModule.class);
|
||||
|
||||
/* end modules */
|
||||
|
||||
if (config.metrics()) {
|
||||
|
@ -155,7 +167,7 @@ public class TweaksPlugin extends MStatsPlugin {
|
|||
mStats(1);
|
||||
}
|
||||
|
||||
DebugLogger.fine("Took %.3f milliseconds".formatted((System.nanoTime() - start) / 1000000.0));
|
||||
DebugLogger.fine("Took %.3f milliseconds", (System.nanoTime() - start) / 1000000.0);
|
||||
}
|
||||
|
||||
private String getTargetVersion() {
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.chat;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ChatCommands implements CommandExecutor {
|
||||
private final ChatModule manager;
|
||||
|
||||
public ChatCommands(ChatModule manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, Command command, @NotNull String label, String[] args) {
|
||||
if (command.getName().equals("chat")) {
|
||||
Player player = (Player) sender;
|
||||
ChatRoom chatRoom = manager.getPlayerChatRoom(player);
|
||||
|
||||
if (args.length == 0) { // show room
|
||||
player.spigot().sendMessage(chatRoom.getInfoComponent());
|
||||
} else { // join room
|
||||
String id = args[0];
|
||||
|
||||
if (id.equals(chatRoom.id)) {
|
||||
sender.spigot().sendMessage(Language.getComponent("chatAlreadyHere", ChatColor.GRAY));
|
||||
return true;
|
||||
}
|
||||
|
||||
String password = null;
|
||||
if (args.length > 1) {
|
||||
password = Arrays.stream(args).skip(1).collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
boolean authenticated = false;
|
||||
BaseComponent component = null;
|
||||
ChatRoom newRoom = manager.getById(id);
|
||||
if (newRoom != null) {
|
||||
if (newRoom.password != null) {
|
||||
if (newRoom.password.equals(password)) {
|
||||
authenticated = true;
|
||||
} else if (password == null) {
|
||||
component = Language.getComponent("chatPasswordProtected", ChatColor.RED);
|
||||
} else {
|
||||
component = Language.getComponent("chatWrongPassword", ChatColor.RED);
|
||||
}
|
||||
} else {
|
||||
authenticated = true;
|
||||
}
|
||||
} else {
|
||||
component = Language.getComponent("chatNoSuchRoom", ChatColor.RED, id);
|
||||
}
|
||||
|
||||
if (authenticated) {
|
||||
/*component = new ComponentBuilder(Language.getComponent("chatJoined", ChatColor.GOLD))
|
||||
.append(" ")
|
||||
.append(ChatFormatUtils.formatChatRoom(chatRoom)).color(newRoom.color)
|
||||
.build();*/
|
||||
player.sendMessage("");
|
||||
manager.setPlayerChatRoom(newRoom, player);
|
||||
} else {
|
||||
player.spigot().sendMessage(component);
|
||||
}
|
||||
|
||||
}
|
||||
} else if (command.getName().equals("chatmanage")) {
|
||||
Player player = (Player) sender;
|
||||
ChatRoom chatRoom = manager.getPlayerChatRoom(player);
|
||||
boolean isOwner = player.equals(chatRoom.owner);
|
||||
|
||||
if (args.length > 1) {
|
||||
String action = args[0];
|
||||
String argument = args[1];
|
||||
|
||||
switch (action) {
|
||||
case "create" -> {
|
||||
try {
|
||||
ChatRoom newRoom = manager.createChatRoom(argument, null, player);
|
||||
sender.sendMessage("Created a chat room. Join it: /c " + newRoom.id);
|
||||
sender.sendMessage("You might also want to protect it with a password: /cm setpassword");
|
||||
} catch (ChatModule.InvalidIdException e) {
|
||||
sender.sendMessage("ID is invalid: " + e.getMessage());
|
||||
} catch (ChatModule.ChatRoomExistsException e) {
|
||||
sender.sendMessage("Room %s already exists".formatted(argument));
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Failed to create room");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
case "delete" -> {
|
||||
if (argument.equals(chatRoom.id)) {
|
||||
if (isOwner) {
|
||||
manager.deleteChatRoom(chatRoom);
|
||||
sender.sendMessage("Room %s deleted".formatted(chatRoom.id));
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Pass %s as an argument to confirm".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case "setowner" -> {
|
||||
if (isOwner) {
|
||||
Player newOwner = Bukkit.getPlayer(argument);
|
||||
if (newOwner != null && newOwner.isOnline()) {
|
||||
chatRoom.owner = newOwner;
|
||||
try {
|
||||
manager.saveChatRoom(chatRoom);
|
||||
sender.sendMessage("Owner changed to " + newOwner.getName());
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Failed to change owner");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Player must be online");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case "setpassword" -> {
|
||||
if (isOwner) {
|
||||
chatRoom.password = Arrays.stream(args).skip(1).collect(Collectors.joining(" "));
|
||||
try {
|
||||
manager.saveChatRoom(chatRoom);
|
||||
sender.sendMessage("Password changed");
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Failed to change password");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case "setcolor" -> {
|
||||
if (isOwner) {
|
||||
ChatColor newColor = ChatColor.of(argument);
|
||||
if (newColor != null) {
|
||||
chatRoom.color = newColor;
|
||||
try {
|
||||
manager.saveChatRoom(chatRoom);
|
||||
sender.sendMessage("Message color changed to " + newColor.getName());
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Failed to change color");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Invalid color");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
sender.sendMessage("Actions: create, delete, setowner, setpassword, setcolor");
|
||||
}
|
||||
}
|
||||
} else if (args.length > 0) {
|
||||
switch (args[0]) {
|
||||
case "create" ->
|
||||
sender.sendMessage("Please pass a room name as an argument. The room name must be of characters and digits.");
|
||||
case "delete" ->
|
||||
sender.sendMessage("You want to delete room %s. Confirm by passing its name as an argument for this action.".formatted(chatRoom.id));
|
||||
case "setowner" ->
|
||||
sender.sendMessage("To transfer ownership of room %s, pass the new owner name as an argument for this action.".formatted(chatRoom.id));
|
||||
case "setpassword" ->
|
||||
sender.sendMessage("To change the password of room %s, pass the new password as an argument for this action.".formatted(chatRoom.id));
|
||||
case "setcolor" ->
|
||||
sender.sendMessage("To change the message color of room %s, pass the new color as an argument for this action. #hex or color name.".formatted(chatRoom.id));
|
||||
default ->
|
||||
sender.sendMessage("Actions: create, delete, setowner, setpassword, setcolor");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Actions: create, delete, setowner, setpassword, setcolor");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
143
src/main/java/eu/m724/tweaks/config/ConfigLoader.java
Normal file
143
src/main/java/eu/m724/tweaks/config/ConfigLoader.java
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.config;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class ConfigLoader {
|
||||
private final FileConfiguration configuration;
|
||||
private final List<String> missing = new ArrayList<>();
|
||||
|
||||
ConfigLoader(FileConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
List<String> getMissing() {
|
||||
return missing;
|
||||
}
|
||||
|
||||
TweaksConfig load() {
|
||||
boolean metrics = configuration.getBoolean("metrics", false);
|
||||
boolean debug = configuration.getBoolean("debug", false);
|
||||
String locale = configuration.getString("locale", "US");
|
||||
|
||||
boolean worldborderExpand = getBoolean("worldborder.expand");
|
||||
boolean worldborderHide = getBoolean("worldborder.hide");
|
||||
|
||||
boolean brandEnabled = getBoolean("brand.enabled");
|
||||
String brandText = getString("brand.text");
|
||||
boolean brandShowPing = getBoolean("brand.showPing");
|
||||
boolean brandShowMspt = getBoolean("brand.showMspt");
|
||||
|
||||
boolean doorDoubleOpen = getBoolean("doors.doubleOpen");
|
||||
boolean doorKnocking = getBoolean("doors.knocking");
|
||||
|
||||
boolean motdEnabled = getBoolean("motd.enabled");
|
||||
String motdSet = getString("motd.set");
|
||||
|
||||
boolean chatEnabled = getBoolean("chat.enabled");
|
||||
boolean chatLocalEvents = getBoolean("chat.localEvents");
|
||||
String chatDefaultName = getString("chat.defaultName");
|
||||
int chatRadius = getInt("chat.radius");
|
||||
|
||||
boolean compassEnabled = getBoolean("compass.enabled");
|
||||
int compassWidth = getInt("compass.width");
|
||||
int compassPrecision = getInt("compass.precision");
|
||||
|
||||
boolean pomodoroEnabled = getBoolean("pomodoro.enabled");
|
||||
boolean pomodoroForce = getBoolean("pomodoro.force");
|
||||
|
||||
boolean updaterEnabled = getBoolean("updater.enabled");
|
||||
|
||||
boolean hardcoreEnabled = getBoolean("hardcore.enabled");
|
||||
double hardcoreChance = getDouble("hardcore.chance");
|
||||
|
||||
boolean sleepEnabled = getBoolean("sleep.enabled");
|
||||
boolean sleepInstant = getBoolean("sleep.instant");
|
||||
double sleepHeal = getDouble("sleep.heal");
|
||||
|
||||
boolean authEnabled = getBoolean("auth.enabled");
|
||||
boolean authForce = getBoolean("auth.force");
|
||||
String authHostname = getString("auth.domain");
|
||||
|
||||
boolean redstoneEnabled = getBoolean("retstone.enabled");
|
||||
String redstoneListen = getString("retstone.listen");
|
||||
|
||||
// this is processed when initing knockback module
|
||||
Map<String, Object> knockbackModifiers = getValues("knockback");
|
||||
|
||||
boolean killswitchEnabled = getBoolean("killswitch.enabled");
|
||||
String killswitchListen = getString("killswitch.listen");
|
||||
|
||||
boolean swingEnabled = getBoolean("swing.enabled");
|
||||
|
||||
return new TweaksConfig(
|
||||
metrics, debug, locale,
|
||||
worldborderExpand, worldborderHide,
|
||||
brandEnabled, brandText, brandShowPing, brandShowMspt,
|
||||
doorDoubleOpen, doorKnocking,
|
||||
motdEnabled, motdSet,
|
||||
chatEnabled, chatLocalEvents, chatDefaultName, chatRadius,
|
||||
compassEnabled, compassWidth, compassPrecision,
|
||||
pomodoroEnabled, pomodoroForce,
|
||||
updaterEnabled,
|
||||
hardcoreEnabled, hardcoreChance,
|
||||
sleepEnabled, sleepInstant, sleepHeal,
|
||||
authEnabled, authForce, authHostname,
|
||||
redstoneEnabled, redstoneListen,
|
||||
knockbackModifiers,
|
||||
killswitchEnabled, killswitchListen,
|
||||
swingEnabled
|
||||
);
|
||||
}
|
||||
|
||||
private double getDouble(String key) {
|
||||
if (!configuration.contains(key))
|
||||
missing.add(key);
|
||||
|
||||
// we return the whatever default value
|
||||
return configuration.getDouble(key);
|
||||
}
|
||||
|
||||
private int getInt(String key) {
|
||||
if (!configuration.contains(key))
|
||||
missing.add(key);
|
||||
|
||||
return configuration.getInt(key);
|
||||
}
|
||||
|
||||
private boolean getBoolean(String key) {
|
||||
if (!configuration.contains(key))
|
||||
missing.add(key);
|
||||
|
||||
return configuration.getBoolean(key);
|
||||
}
|
||||
|
||||
private String getString(String key) {
|
||||
if (!configuration.contains(key))
|
||||
missing.add(key);
|
||||
|
||||
return configuration.getString(key);
|
||||
}
|
||||
|
||||
private Map<String, Object> getValues(String key) {
|
||||
var cs = configuration.getConfigurationSection(key);
|
||||
|
||||
if (cs == null) {
|
||||
missing.add(key);
|
||||
// the default is null, which is bad
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
return cs.getValues(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class MissingFieldsException extends Exception {
|
||||
private final List<String> missing;
|
||||
|
||||
MissingFieldsException(List<String> missing) {
|
||||
this.missing = missing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.join(", ", missing);
|
||||
}
|
||||
}
|
103
src/main/java/eu/m724/tweaks/config/TweaksConfig.java
Normal file
103
src/main/java/eu/m724/tweaks/config/TweaksConfig.java
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.config;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record TweaksConfig(
|
||||
boolean metrics,
|
||||
boolean debug,
|
||||
String locale,
|
||||
|
||||
boolean worldborderExpand,
|
||||
boolean worldborderHide,
|
||||
|
||||
boolean brandEnabled,
|
||||
String brandText,
|
||||
boolean brandShowPing,
|
||||
boolean brandShowMspt,
|
||||
|
||||
boolean doorDoubleOpen,
|
||||
boolean doorKnocking,
|
||||
|
||||
boolean motdEnabled,
|
||||
String motdSet,
|
||||
|
||||
boolean chatEnabled,
|
||||
boolean chatLocalEvents,
|
||||
String chatDefaultName,
|
||||
int chatRadius,
|
||||
|
||||
boolean compassEnabled,
|
||||
int compassWidth,
|
||||
int compassPrecision,
|
||||
|
||||
boolean pomodoroEnabled,
|
||||
boolean pomodoroForce,
|
||||
|
||||
boolean updaterEnabled,
|
||||
|
||||
boolean hardcoreEnabled,
|
||||
double hardcoreChance,
|
||||
|
||||
boolean sleepEnabled,
|
||||
boolean sleepInstant,
|
||||
double sleepHeal,
|
||||
|
||||
boolean authEnabled,
|
||||
boolean authForce,
|
||||
String authDomain,
|
||||
|
||||
boolean redstoneEnabled,
|
||||
String redstoneListen,
|
||||
|
||||
Map<String, Object> knockbackModifiers,
|
||||
|
||||
boolean killswitchEnabled,
|
||||
String killswitchListen,
|
||||
|
||||
boolean swingEnabled
|
||||
) {
|
||||
public static final int CONFIG_VERSION = 2;
|
||||
private static TweaksConfig config;
|
||||
|
||||
public static TweaksConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public static TweaksConfig load(Plugin plugin) throws Exception {
|
||||
plugin.saveDefaultConfig();
|
||||
var pluginConfig = plugin.getConfig();
|
||||
|
||||
var configVersion = pluginConfig.getInt("magic number don't modify this", 0);
|
||||
var exception = new RuntimeException("Config version is %d, expected %d".formatted(configVersion, CONFIG_VERSION));
|
||||
|
||||
if (configVersion == 0) {
|
||||
throw exception;
|
||||
} else if (configVersion < CONFIG_VERSION) {
|
||||
throw new Exception("Please follow update instructions https://www.spigotmc.org/resources/tweaks724.121057/updates", exception);
|
||||
} else if (configVersion > CONFIG_VERSION) {
|
||||
throw new Exception("Did you downgrade the plugin? Delete config.yml and let the plugin re-create it", exception);
|
||||
}
|
||||
|
||||
var loader = new ConfigLoader(pluginConfig);
|
||||
var config = loader.load();
|
||||
|
||||
if (loader.getMissing().isEmpty()) {
|
||||
TweaksConfig.config = config;
|
||||
return config;
|
||||
} else {
|
||||
throw new Exception(
|
||||
"One or more fields are missing from config.yml. Did you follow the update instructions? https://www.spigotmc.org/resources/tweaks724.121057/updates",
|
||||
new MissingFieldsException(loader.getMissing())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,13 +4,16 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks;
|
||||
package eu.m724.tweaks.module;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import eu.m724.tweaks.TweaksPlugin;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
|
@ -31,7 +34,6 @@ public abstract class TweaksModule {
|
|||
DebugLogger.fine("Initialized %s in %d µs", name, (end - start) / 1000);
|
||||
}
|
||||
|
||||
// TODO not static maybe?
|
||||
protected TweaksPlugin getPlugin() {
|
||||
return TweaksPlugin.getInstance();
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.alert;
|
||||
package eu.m724.tweaks.module.alert;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.alert;
|
||||
package eu.m724.tweaks.module.alert;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
|
@ -4,12 +4,12 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.alert;
|
||||
package eu.m724.tweaks.module.alert;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.alert;
|
||||
package eu.m724.tweaks.module.alert;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
|
@ -1,13 +1,13 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.auth;
|
||||
package eu.m724.tweaks.module.auth;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
|
@ -1,19 +1,21 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.auth;
|
||||
package eu.m724.tweaks.module.auth;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.Language;
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthListener implements Listener {
|
||||
private final AuthStorage authStorage;
|
||||
|
@ -40,6 +42,10 @@ public class AuthListener implements Listener {
|
|||
allowed = true; // key just assigned
|
||||
} catch (FileNotFoundException | AuthStorage.AlreadyClaimedException | AuthStorage.InvalidKeyException e) {
|
||||
allowed = !force; // If forced all players must have a key
|
||||
} catch (IOException e) {
|
||||
DebugLogger.severe("Error assigning key to player. " + e.getMessage());
|
||||
event.disallow(PlayerLoginEvent.Result.KICK_OTHER, Language.getString("authKickError"));
|
||||
allowed = true; // to skip the below checks
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.auth;
|
||||
package eu.m724.tweaks.module.auth;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
|
||||
public class AuthModule extends TweaksModule {
|
||||
@Override
|
|
@ -1,20 +1,25 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.auth;
|
||||
package eu.m724.tweaks.module.auth;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthStorage {
|
||||
private static final char[] KEY_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
|
||||
private static final int KEY_LENGTH = 10;
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private final File playersDirectory;
|
||||
private final File keysDirectory;
|
||||
|
||||
|
@ -71,7 +76,7 @@ public class AuthStorage {
|
|||
byte[] bytes = is.readNBytes(50);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // TODO
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -121,7 +126,7 @@ public class AuthStorage {
|
|||
* @throws FileNotFoundException if no such key
|
||||
* @throws AlreadyClaimedException if key is claimed or user owns another key
|
||||
*/
|
||||
void assignOwner(String key, UUID uuid) throws FileNotFoundException, AlreadyClaimedException {
|
||||
void assignOwner(String key, UUID uuid) throws IOException, FileNotFoundException, AlreadyClaimedException {
|
||||
if (isInvalid(key)) throw new InvalidKeyException();
|
||||
|
||||
if (getUserOfKey(key) != null) throw new AlreadyClaimedException();
|
||||
|
@ -136,34 +141,25 @@ public class AuthStorage {
|
|||
|
||||
try (FileOutputStream os = new FileOutputStream(file)) {
|
||||
os.write(byteBuffer.array());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // TODO
|
||||
}
|
||||
|
||||
File file2 = new File(playersDirectory, uuid.toString());
|
||||
|
||||
try (FileOutputStream os = new FileOutputStream(file2)) {
|
||||
os.write(key.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
// TODO improve
|
||||
String generateKey() {
|
||||
char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
|
||||
Random random = new Random();
|
||||
int length = random.nextInt(8, 10);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
StringBuilder key = new StringBuilder();
|
||||
|
||||
for (int i=0; i<length; i++) {
|
||||
key.append(chars[random.nextInt(chars.length)]);
|
||||
for (int i=0; i<KEY_LENGTH; i++) {
|
||||
builder.append(KEY_CHARS[RANDOM.nextInt(KEY_CHARS.length)]);
|
||||
}
|
||||
|
||||
return key.toString();
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static class InvalidKeyException extends RuntimeException {}
|
||||
static class AlreadyClaimedException extends Exception {}
|
||||
static class InvalidKeyException extends RuntimeException { }
|
||||
static class AlreadyClaimedException extends Exception { }
|
||||
}
|
247
src/main/java/eu/m724/tweaks/module/chat/ChatCommands.java
Normal file
247
src/main/java/eu/m724/tweaks/module/chat/ChatCommands.java
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.chat;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ChatCommands implements CommandExecutor {
|
||||
private final ChatModule manager;
|
||||
|
||||
public ChatCommands(ChatModule manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, Command command, @NotNull String label, String[] args) {
|
||||
if (command.getName().equals("chat")) {
|
||||
Player player = (Player) sender;
|
||||
ChatRoom chatRoom = manager.getPlayerChatRoom(player);
|
||||
|
||||
if (args.length == 0) { // show room
|
||||
player.spigot().sendMessage(chatRoom.getInfoComponent());
|
||||
} else { // join room
|
||||
String id = args[0];
|
||||
|
||||
if (id.equals(chatRoom.id)) {
|
||||
sender.spigot().sendMessage(Language.getComponent("chatAlreadyHere", ChatColor.GRAY));
|
||||
return true;
|
||||
}
|
||||
|
||||
String password = null;
|
||||
if (args.length > 1) {
|
||||
password = Arrays.stream(args).skip(1).collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
boolean authenticated = false;
|
||||
BaseComponent component = null;
|
||||
ChatRoom newRoom = manager.getById(id);
|
||||
if (newRoom != null) {
|
||||
if (newRoom.password != null) {
|
||||
if (newRoom.password.equals(password)) {
|
||||
authenticated = true;
|
||||
} else if (password == null) {
|
||||
component = Language.getComponent("chatPasswordProtected", ChatColor.RED);
|
||||
} else {
|
||||
component = Language.getComponent("chatWrongPassword", ChatColor.RED);
|
||||
}
|
||||
} else {
|
||||
authenticated = true;
|
||||
}
|
||||
} else {
|
||||
if (ChatRoomLoader.validateId(id) == 0) {
|
||||
component = Language.getComponent("chatNoSuchRoom", ChatColor.RED, id);
|
||||
} else {
|
||||
component = Language.getComponent("chatNoSuchRoomInvalidId", ChatColor.RED, id);
|
||||
}
|
||||
}
|
||||
|
||||
if (authenticated) {
|
||||
/*component = new ComponentBuilder(Language.getComponent("chatJoined", ChatColor.GOLD))
|
||||
.append(" ")
|
||||
.append(ChatFormatUtils.formatChatRoom(chatRoom)).color(newRoom.color)
|
||||
.build();*/
|
||||
player.sendMessage("");
|
||||
manager.setPlayerChatRoom(newRoom, player);
|
||||
} else {
|
||||
player.spigot().sendMessage(component);
|
||||
}
|
||||
|
||||
}
|
||||
} else if (command.getName().equals("chatmanage")) {
|
||||
Player player = (Player) sender;
|
||||
ChatRoom chatRoom = manager.getPlayerChatRoom(player);
|
||||
boolean isOwner = player.equals(chatRoom.owner);
|
||||
|
||||
String action = args.length > 0 ? args[0] : null;
|
||||
String argument = args.length > 1 ? args[1] : null;
|
||||
|
||||
switch (action) {
|
||||
case "create" -> {
|
||||
if (argument == null) {
|
||||
sender.sendMessage("Please pass a room name as an argument. The room name can contain only characters and digits.");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
ChatRoom newRoom = manager.createChatRoom(argument, null, player);
|
||||
|
||||
var component = new ComponentBuilder("Created a chat room. Join it: ").color(ChatColor.GOLD)
|
||||
.append("/c " + newRoom.id).color(ChatColor.AQUA)
|
||||
.event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/c " + newRoom.id))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Language.getString("clickToExecuteCommand"))))
|
||||
.append("\n")
|
||||
.append("To protect it with a password, join it and use ").color(ChatColor.GRAY)
|
||||
.append("/cm setpassword <password>").color(ChatColor.DARK_PURPLE)
|
||||
.event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/cm setpassword "))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Language.getString("clickToExecuteCommand"))));
|
||||
|
||||
sender.spigot().sendMessage(component.build());
|
||||
} catch (ChatModule.InvalidIdException e) {
|
||||
var component = new ComponentBuilder("ID is invalid: ").color(ChatColor.GRAY)
|
||||
.append(e.getMessage()).color(ChatColor.RED);
|
||||
|
||||
sender.spigot().sendMessage(component.build());
|
||||
} catch (ChatModule.ChatRoomExistsException e) {
|
||||
sender.sendMessage("Room %s already exists".formatted(argument));
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Error creating room");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
case "delete" -> {
|
||||
if (isOwner) {
|
||||
if (argument == null) {
|
||||
sender.sendMessage("You want to delete room \"%s\". Confirm by passing the ID as an argument.".formatted(chatRoom.id));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (argument.equals(chatRoom.id)) {
|
||||
manager.deleteChatRoom(chatRoom);
|
||||
sender.sendMessage("Room %s deleted".formatted(chatRoom.id));
|
||||
} else {
|
||||
sender.sendMessage("Pass \"%s\" as an argument to confirm".formatted(chatRoom.id));
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case "setowner" -> {
|
||||
if (isOwner) {
|
||||
if (argument == null) {
|
||||
sender.sendMessage("To transfer ownership of room %s, pass the new owner name as an argument for this action.".formatted(chatRoom.id));
|
||||
return true;
|
||||
}
|
||||
|
||||
Player newOwner = Bukkit.getPlayer(argument);
|
||||
if (newOwner != null && newOwner.isOnline()) {
|
||||
chatRoom.owner = newOwner;
|
||||
try {
|
||||
manager.saveChatRoom(chatRoom);
|
||||
sender.sendMessage("Owner changed to " + newOwner.getName());
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Error changing owner");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Player must be online");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case "setpassword" -> {
|
||||
if (isOwner) {
|
||||
if (argument == null) {
|
||||
sender.sendMessage("To change the password of room %s, pass the new password as an argument for this action.".formatted(chatRoom.id));
|
||||
return true;
|
||||
}
|
||||
|
||||
chatRoom.password = Arrays.stream(args).skip(1).collect(Collectors.joining(" "));
|
||||
try {
|
||||
manager.saveChatRoom(chatRoom);
|
||||
|
||||
var component = new ComponentBuilder("Password changed to ").color(ChatColor.GREEN)
|
||||
.append("(hover to view)").color(ChatColor.AQUA)
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(chatRoom.password)))
|
||||
.append("\n")
|
||||
.append("To unset, ").color(ChatColor.GRAY)
|
||||
.append("/cm unsetpassword").color(ChatColor.DARK_PURPLE)
|
||||
.event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/cm unsetpassword"))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Language.getString("clickToExecuteCommand"))));
|
||||
|
||||
sender.spigot().sendMessage(component.build());
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Error changing password");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case "unsetpassword" -> {
|
||||
if (isOwner) {
|
||||
chatRoom.password = null;
|
||||
try {
|
||||
manager.saveChatRoom(chatRoom);
|
||||
sender.sendMessage("Password removed from " + chatRoom.id);
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Error removing password");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case "setcolor" -> {
|
||||
if (isOwner) {
|
||||
if (argument == null) {
|
||||
sender.sendMessage("To change the message color of room %s, pass the new color as an argument for this action. #hex or color name.".formatted(chatRoom.id));
|
||||
return true;
|
||||
}
|
||||
|
||||
ChatColor newColor = ChatColor.of(argument);
|
||||
if (newColor != null) {
|
||||
chatRoom.color = newColor;
|
||||
try {
|
||||
manager.saveChatRoom(chatRoom);
|
||||
sender.sendMessage("Message color changed to " + newColor.getName());
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage("Error changing color");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Invalid color");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You're not the owner of %s, please enter the room you want to make changes in".formatted(chatRoom.id));
|
||||
}
|
||||
}
|
||||
case null, default -> {
|
||||
sender.sendMessage("Actions: create, delete, setowner, setpassword, unsetpassword, setcolor");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.chat;
|
||||
package eu.m724.tweaks.module.chat;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.chat;
|
||||
package eu.m724.tweaks.module.chat;
|
||||
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.chat;
|
||||
package eu.m724.tweaks.module.chat;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
@ -32,12 +32,14 @@ public class ChatModule extends TweaksModule {
|
|||
throw new RuntimeException("Please disable enforce-secure-profile in server.properties to use chatrooms");
|
||||
}
|
||||
|
||||
ChatRoomLoader.init(getPlugin());
|
||||
getById(defaultRoom);
|
||||
registerEvents(new ChatListener(this));
|
||||
|
||||
var chatCommands = new ChatCommands(this);
|
||||
registerCommand("chat", chatCommands);
|
||||
registerCommand("chatmanage", chatCommands);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,11 +155,11 @@ public class ChatModule extends TweaksModule {
|
|||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
throw new InvalidIdException("ID is too short, make it at least 2 chars");
|
||||
throw new InvalidIdException("ID is too short, it must be at least 2 chars long");
|
||||
case 2:
|
||||
throw new InvalidIdException("ID is too long, make it 20 chars or shorter");
|
||||
throw new InvalidIdException("ID is too long, it mustn't be longer than 20 chars");
|
||||
case 4:
|
||||
throw new InvalidIdException("ID must be composed from characters a-z and numbers 0-9");
|
||||
throw new InvalidIdException("ID must be of characters a-z and numbers 0-9");
|
||||
}
|
||||
|
||||
if (getById(id) != null)
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.chat;
|
||||
package eu.m724.tweaks.module.chat;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.chat;
|
||||
package eu.m724.tweaks.module.chat;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
|
@ -29,7 +29,7 @@ public class ChatRoomLoader {
|
|||
*/
|
||||
static File getFile(String id) {
|
||||
if (validateId(id) != 0)
|
||||
throw new RuntimeException("Invalid id: " + id);
|
||||
return null;
|
||||
|
||||
return new File(chatRoomsDir, id + ".yml");
|
||||
}
|
||||
|
@ -66,7 +66,8 @@ public class ChatRoomLoader {
|
|||
*/
|
||||
static ChatRoom load(String id) {
|
||||
File chatRoomFile = getFile(id);
|
||||
if (!chatRoomFile.exists()) return null;
|
||||
if (chatRoomFile == null || !chatRoomFile.exists())
|
||||
return null;
|
||||
|
||||
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(chatRoomFile);
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.compass;
|
||||
package eu.m724.tweaks.module.compass;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.compass;
|
||||
package eu.m724.tweaks.module.compass;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.door;
|
||||
package eu.m724.tweaks.module.door;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.door;
|
||||
package eu.m724.tweaks.module.door;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.durability;
|
||||
|
||||
import eu.m724.tweaks.TweaksPlugin;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class DPlayerProperties implements Listener {
|
||||
private final NamespacedKey namespacedKey = new NamespacedKey(TweaksPlugin.getInstance(), "durability_enabled");
|
||||
private final Set<Player> players = new HashSet<>();
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
var player = event.getPlayer();
|
||||
|
||||
if (player.hasPermission("tweaks724.durabilityalert")) {
|
||||
var enabled = player.getPersistentDataContainer().get(namespacedKey, PersistentDataType.BOOLEAN);
|
||||
if (enabled != null && enabled) {
|
||||
players.add(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
players.remove(event.getPlayer());
|
||||
}
|
||||
|
||||
Set<Player> getPlayers() {
|
||||
return Set.copyOf(players);
|
||||
}
|
||||
|
||||
boolean isPlayerEnabled(Player player) {
|
||||
return players.contains(player);
|
||||
}
|
||||
|
||||
void disableForPlayer(Player player) {
|
||||
players.remove(player);
|
||||
player.getPersistentDataContainer().set(namespacedKey, PersistentDataType.BOOLEAN, false);
|
||||
}
|
||||
|
||||
void enableForPlayer(Player player) {
|
||||
players.add(player);
|
||||
player.getPersistentDataContainer().set(namespacedKey, PersistentDataType.BOOLEAN, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.durability;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DurabilityCaches {
|
||||
private final Map<Player, Long> lastFullReminder = new HashMap<>();
|
||||
// BAD
|
||||
private final Map<Player, Map<Material, Long>> lastUse = new HashMap<>();
|
||||
// BAD
|
||||
private final Map<Player, Map<Material, Long>> lastPing = new HashMap<>();
|
||||
|
||||
boolean shouldFullRemind(Player player, long now) {
|
||||
var lfr = lastFullReminder.getOrDefault(player, 0L);
|
||||
|
||||
if (now - lfr > 300 * 1000) {
|
||||
lastFullReminder.put(player, now);
|
||||
return true;
|
||||
} else if (now - lfr < 3 * 1000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean shouldRemind(Player player, ItemStack itemStack, long now) {
|
||||
var lu = lastUse.computeIfAbsent(player, (k) -> new HashMap<>()).getOrDefault(itemStack.getType(), 0L);
|
||||
|
||||
if (now - lu > 180 * 1000) {
|
||||
lastUse.get(player).put(itemStack.getType(), now);
|
||||
return true;
|
||||
} else if (now - lu < 3 * 1000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean shouldPing(Player player, ItemStack itemStack, long now) {
|
||||
var lp = lastPing.computeIfAbsent(player, (k) -> new HashMap<>()).getOrDefault(itemStack.getType(), 0L);
|
||||
|
||||
if (now - lp > 60 * 1000) {
|
||||
lastPing.get(player).put(itemStack.getType(), now);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.durability;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
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 DurabilityCommands implements CommandExecutor {
|
||||
private final DPlayerProperties properties;
|
||||
|
||||
public DurabilityCommands(DPlayerProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Only players can use this command");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (properties.isPlayerEnabled(player)) {
|
||||
properties.disableForPlayer(player);
|
||||
sender.spigot().sendMessage(Language.getComponent("durabilityDisabled", ChatColor.GRAY));
|
||||
} else {
|
||||
properties.enableForPlayer(player);
|
||||
sender.spigot().sendMessage(Language.getComponent("durabilityEnabled", ChatColor.GRAY));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.durability;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerItemDamageEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.Damageable;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class DurabilityModule extends TweaksModule implements Listener {
|
||||
private final DurabilityCaches cache = new DurabilityCaches();
|
||||
private final DPlayerProperties properties = new DPlayerProperties();
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
registerEvents(this);
|
||||
|
||||
registerCommand("durabilityalert", new DurabilityCommands(properties));
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
properties.getPlayers().forEach(p -> refreshBar(p));
|
||||
}
|
||||
}.runTaskTimerAsynchronously(getPlugin(), 0, 40);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerItemDamage(PlayerItemDamageEvent event) {
|
||||
refreshBar(event.getPlayer(), event.getItem(), event.getDamage());
|
||||
}
|
||||
|
||||
private void refreshBar(Player player) {
|
||||
refreshBar(player, null, -1);
|
||||
}
|
||||
|
||||
private void refreshBar(Player player, ItemStack justDamaged, int damage) {
|
||||
if (!properties.isPlayerEnabled(player)) return;
|
||||
|
||||
var items = new ItemStack[] {
|
||||
player.getInventory().getHelmet(),
|
||||
player.getInventory().getChestplate(),
|
||||
player.getInventory().getLeggings(),
|
||||
player.getInventory().getBoots(),
|
||||
player.getInventory().getItemInMainHand(),
|
||||
player.getInventory().getItemInOffHand()
|
||||
};
|
||||
|
||||
var builder = new ComponentBuilder();
|
||||
|
||||
var now = System.currentTimeMillis();
|
||||
var all = cache.shouldFullRemind(player, now);
|
||||
|
||||
for (var itemStack : items) {
|
||||
if (itemStack == null || !itemStack.hasItemMeta()) continue;
|
||||
|
||||
if (itemStack.getItemMeta() instanceof Damageable meta) {
|
||||
var target = itemStack.equals(justDamaged);
|
||||
|
||||
var maxDurability = itemStack.getType().getMaxDurability();
|
||||
var durability = maxDurability - meta.getDamage() - (target ? damage : 0);
|
||||
durability = Math.max(0, durability);
|
||||
var percentage = (double) durability / maxDurability;
|
||||
|
||||
var notify = durability < 30 && (durability < 10 || percentage < 0.1);
|
||||
|
||||
var remind = cache.shouldRemind(player, itemStack, now);
|
||||
var important = target && notify && cache.shouldPing(player, itemStack, now);
|
||||
|
||||
DebugLogger.finer("%s's %s: %d / %d (%.2f%%)%s%s", player.getName(), itemStack.getType().name(), durability, maxDurability, percentage * 100, notify ? " notify" : "", important ? " important" : "");
|
||||
|
||||
if (notify || all || remind) {
|
||||
var longName = remind || important;
|
||||
var label = longName ? getMaterialLongName(itemStack.getType()) : getMaterialShortName(itemStack.getType());
|
||||
var labelColor = percentage > 0 ? matColor(itemStack.getType()) : ChatColor.DARK_RED;
|
||||
|
||||
var percentageStr = (int) (percentage * 100) + "%";
|
||||
var percentageColor = mixColor(labelColor, ChatColor.DARK_RED, 1.0 - percentage * 10);
|
||||
|
||||
builder.append(label + " ").color(labelColor);
|
||||
builder.append(percentageStr + " ").color(percentageColor);
|
||||
|
||||
if (important) {
|
||||
player.playSound(player, Sound.BLOCK_ANVIL_PLACE, 0.5f, 1.5f);
|
||||
player.sendTitle("", labelColor + label + " " + percentageColor + percentageStr, 5, 20, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var component = builder.create();
|
||||
if (component.length > 0)
|
||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, component);
|
||||
}
|
||||
|
||||
private String getMaterialLongName(Material material) {
|
||||
var sp = material.name().split("_");
|
||||
var str = sp[sp.length - 1];
|
||||
return str.charAt(0) + str.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
private String getMaterialShortName(Material material) {
|
||||
return getMaterialLongName(material).substring(0, 2);
|
||||
}
|
||||
|
||||
private ChatColor mixColor(ChatColor from, ChatColor to, double percentage) {
|
||||
percentage = Math.clamp(percentage, 0.0, 1.0);
|
||||
|
||||
var diffR = to.getColor().getRed() - from.getColor().getRed();
|
||||
var diffG = to.getColor().getGreen() - from.getColor().getGreen();
|
||||
var diffB = to.getColor().getBlue() - from.getColor().getBlue();
|
||||
|
||||
var r = from.getColor().getRed() + (int) (diffR * percentage);
|
||||
var g = from.getColor().getGreen() + (int) (diffG * percentage);
|
||||
var b = from.getColor().getBlue() + (int) (diffB * percentage);
|
||||
|
||||
return ChatColor.of(new Color(r, g, b));
|
||||
}
|
||||
|
||||
private ChatColor matColor(Material material) {
|
||||
var color = ChatColor.DARK_GRAY;
|
||||
|
||||
if (material.name().startsWith("DIAMOND_")) {
|
||||
color = ChatColor.AQUA;
|
||||
} else if (material.name().startsWith("NETHERITE_")) {
|
||||
color = ChatColor.DARK_PURPLE;
|
||||
} else if (material.name().startsWith("IRON_")) {
|
||||
color = ChatColor.WHITE;
|
||||
} else if (material.name().startsWith("STONE_")) {
|
||||
color = ChatColor.GRAY;
|
||||
} else if (material.name().startsWith("WOODEN_")) {
|
||||
color = ChatColor.DARK_GREEN;
|
||||
} else if (material.name().startsWith("GOLDEN_")) {
|
||||
color = ChatColor.GOLD;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.full;
|
||||
package eu.m724.tweaks.module.full;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
|
@ -4,12 +4,12 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.hardcore;
|
||||
package eu.m724.tweaks.module.hardcore;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus;
|
||||
|
|
@ -4,13 +4,13 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.killswitch;
|
||||
package eu.m724.tweaks.module.killswitch;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.killswitch;
|
||||
package eu.m724.tweaks.module.killswitch;
|
||||
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
|
@ -4,10 +4,10 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.knockback;
|
||||
package eu.m724.tweaks.module.knockback;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
|
@ -4,17 +4,17 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.motd;
|
||||
package eu.m724.tweaks.module.motd;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.events.InternalStructure;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.minecraft.SharedConstants;
|
||||
|
@ -22,44 +22,45 @@ import net.minecraft.core.RegistryAccess;
|
|||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.status.ServerStatus;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class MotdModule extends TweaksModule {
|
||||
private Component[] motds;
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
// TODO adding more MOTD features would require checking whether to enable set
|
||||
|
||||
String motdSetName = TweaksConfig.getConfig().motdSet();
|
||||
String motdSetPath = "motd sets/" + motdSetName + ".txt";
|
||||
File motdSetsFile = new File(getPlugin().getDataFolder(), motdSetPath);
|
||||
Path motdSetPath = getPlugin().getDataFolder().toPath().resolve("motd sets").resolve(getConfig().motdSet() + ".txt");
|
||||
|
||||
// create "motd sets" directory
|
||||
motdSetsFile.getParentFile().mkdirs();
|
||||
try {
|
||||
Files.createDirectories(motdSetPath);
|
||||
} catch (FileAlreadyExistsException ignored) {
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// if this is a builtin set
|
||||
if (!motdSetsFile.exists() && getPlugin().hasResource(motdSetPath))
|
||||
getPlugin().saveResource(motdSetPath, false);
|
||||
if (!Files.exists(motdSetPath) && getPlugin().hasResource("motd sets/" + motdSetPath.getFileName()))
|
||||
getPlugin().saveResource("motd sets/" + motdSetPath.getFileName(), false);
|
||||
|
||||
if (!motdSetsFile.exists()) {
|
||||
throw new RuntimeException("MOTD set \"%s\" doesn't exist".formatted(motdSetName));
|
||||
if (!Files.exists(motdSetPath)) {
|
||||
throw new RuntimeException("MOTD set \"%s\" doesn't exist".formatted(getConfig().motdSet()));
|
||||
}
|
||||
|
||||
String fileContent;
|
||||
try {
|
||||
fileContent = Files.readString(motdSetsFile.toPath());
|
||||
fileContent = Files.readString(motdSetPath);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Reading motd set", e);
|
||||
}
|
||||
|
||||
// MOTDs are split with an empty line
|
||||
motds = Arrays.stream(fileContent.split("\n\n"))
|
||||
Component[] motds = Arrays.stream(fileContent.split("\n\n"))
|
||||
.map(entry -> {
|
||||
entry = entry.strip();
|
||||
JsonElement json = null;
|
|
@ -1,16 +1,16 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.ping;
|
||||
package eu.m724.tweaks.module.ping;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import net.minecraft.network.protocol.common.custom.BrandPayload;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.ping;
|
||||
package eu.m724.tweaks.module.ping;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.ping;
|
||||
package eu.m724.tweaks.module.ping;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.ping;
|
||||
package eu.m724.tweaks.module.ping;
|
||||
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.ping;
|
||||
package eu.m724.tweaks.module.ping;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.ping;
|
||||
package eu.m724.tweaks.module.ping;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.pomodoro;
|
||||
package eu.m724.tweaks.module.pomodoro;
|
||||
|
||||
public class PlayerPomodoro {
|
||||
private int pomodori = 0;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.pomodoro;
|
||||
package eu.m724.tweaks.module.pomodoro;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
|
@ -1,12 +1,12 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.pomodoro;
|
||||
package eu.m724.tweaks.module.pomodoro;
|
||||
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
|
@ -4,15 +4,15 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.pomodoro;
|
||||
package eu.m724.tweaks.module.pomodoro;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
|
||||
public class PomodoroModule extends TweaksModule {
|
||||
@Override
|
||||
protected void onInit() {
|
||||
registerEvents(new PomodoroListener());
|
||||
new PomodoroRunnable(getPlugin()).runTaskTimerAsynchronously(getPlugin(), 0, 20L);
|
||||
new PomodoroRunnable().runTaskTimerAsynchronously(getPlugin(), 0, 20L);
|
||||
|
||||
registerCommand("pomodoro", new PomodoroCommands());
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.pomodoro;
|
||||
package eu.m724.tweaks.module.pomodoro;
|
||||
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.TweaksPlugin;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
|
@ -15,15 +16,12 @@ import org.bukkit.scheduler.BukkitRunnable;
|
|||
|
||||
public class PomodoroRunnable extends BukkitRunnable {
|
||||
private final boolean force = TweaksConfig.getConfig().pomodoroForce();
|
||||
private final Plugin plugin;
|
||||
|
||||
public PomodoroRunnable(Plugin plugin) {
|
||||
this.plugin = plugin; // only used for kicking
|
||||
}
|
||||
private final Plugin plugin = TweaksPlugin.getInstance(); // used only to kick
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long now = System.nanoTime();
|
||||
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
PlayerPomodoro pomodoro = Pomodoros.get(player);
|
||||
if (pomodoro == null) return;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.pomodoro;
|
||||
package eu.m724.tweaks.module.pomodoro;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.redstone;
|
||||
package eu.m724.tweaks.module.redstone;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.Language;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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;
|
||||
package eu.m724.tweaks.module.redstone;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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;
|
||||
package eu.m724.tweaks.module.redstone;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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;
|
||||
package eu.m724.tweaks.module.redstone;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.Language;
|
|
@ -4,11 +4,11 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.redstone;
|
||||
package eu.m724.tweaks.module.redstone;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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;
|
||||
package eu.m724.tweaks.module.redstone;
|
||||
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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;
|
||||
package eu.m724.tweaks.module.redstone;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import eu.m724.tweaks.DebugLogger;
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.sleep;
|
||||
package eu.m724.tweaks.module.sleep;
|
||||
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import eu.m724.tweaks.config.TweaksConfig;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.entity.Player;
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.sleep;
|
||||
package eu.m724.tweaks.module.sleep;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
|
||||
public class SleepModule extends TweaksModule {
|
||||
@Override
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Copyright (C) 2025 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.sleep;
|
||||
package eu.m724.tweaks.module.sleep;
|
||||
|
||||
public class SleepState {
|
||||
static int playersSleeping;
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.sleep;
|
||||
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
public class TimeForwardRunnable extends BukkitRunnable {
|
||||
private final Server server;
|
||||
|
||||
public TimeForwardRunnable(Plugin plugin) {
|
||||
this.server = plugin.getServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (World world : server.getWorlds()) {
|
||||
var gameRuleValue = world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE);
|
||||
if (gameRuleValue == null) gameRuleValue = 100;
|
||||
double percentage = gameRuleValue / 100.0;
|
||||
|
||||
int playersSleeping = SleepState.playersSleeping;
|
||||
//System.out.println(playersSleeping);
|
||||
if (playersSleeping == 0) return;
|
||||
|
||||
int onlinePlayers = (int) (world.getPlayers().size() / percentage);
|
||||
|
||||
double sleepPercentage = (double) playersSleeping / onlinePlayers;
|
||||
|
||||
// we want sleep to take 200 ticks which is 10 seconds assuming all palyres onilien
|
||||
|
||||
long time = world.getTime();
|
||||
long untilDay = 23459 - time;
|
||||
|
||||
if (untilDay == 0) return;
|
||||
|
||||
long perSkip = 200 + (100000 / -untilDay);
|
||||
perSkip = Math.clamp(perSkip, 20, 200);
|
||||
perSkip = (long) (perSkip * sleepPercentage);
|
||||
|
||||
world.setTime(world.getTime() + perSkip);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,10 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.swing;
|
||||
package eu.m724.tweaks.module.swing;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.entity.Entity;
|
|
@ -4,11 +4,11 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater;
|
||||
package eu.m724.tweaks.module.updater;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import eu.m724.tweaks.updater.backend.UpdateChecker;
|
||||
import eu.m724.tweaks.updater.object.VersionedResource;
|
||||
import eu.m724.tweaks.module.updater.backend.UpdateChecker;
|
||||
import eu.m724.tweaks.module.updater.object.VersionedResource;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
|
@ -4,15 +4,15 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater;
|
||||
package eu.m724.tweaks.module.updater;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.updater.backend.UpdateChecker;
|
||||
import eu.m724.tweaks.updater.backend.VersionCache;
|
||||
import eu.m724.tweaks.updater.object.ResourceVersion;
|
||||
import eu.m724.tweaks.updater.object.SpigotResource;
|
||||
import eu.m724.tweaks.updater.object.VersionedResource;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import eu.m724.tweaks.module.updater.backend.UpdateChecker;
|
||||
import eu.m724.tweaks.module.updater.backend.VersionCache;
|
||||
import eu.m724.tweaks.module.updater.object.ResourceVersion;
|
||||
import eu.m724.tweaks.module.updater.object.SpigotResource;
|
||||
import eu.m724.tweaks.module.updater.object.VersionedResource;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
|
@ -4,11 +4,11 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater.backend;
|
||||
package eu.m724.tweaks.module.updater.backend;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.Language;
|
||||
import eu.m724.tweaks.updater.object.VersionedResource;
|
||||
import eu.m724.tweaks.module.updater.object.VersionedResource;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -39,6 +39,7 @@ public class UpdateChecker extends BukkitRunnable {
|
|||
DebugLogger.fine("Checking for updates");
|
||||
lastChecked = System.currentTimeMillis();
|
||||
availableUpdates.clear();
|
||||
var errors = 0;
|
||||
|
||||
for (VersionedResource versionedResource : Set.copyOf(resources)) {
|
||||
String pluginName = versionedResource.resource().plugin().getName();
|
||||
|
@ -50,10 +51,17 @@ public class UpdateChecker extends BukkitRunnable {
|
|||
resources.remove(versionedResource);
|
||||
|
||||
if (newResource.running() == null) {
|
||||
DebugLogger.warning("Unable to find installed version of %s", pluginName);
|
||||
if (versionedResource.running() != null) {
|
||||
DebugLogger.warning("Did you downgrade %s? If so, clear cache", pluginName);
|
||||
var pluginVersion = versionedResource.resource().plugin().getDescription().getVersion();
|
||||
var message = "";
|
||||
|
||||
if (pluginVersion.endsWith("-SNAPSHOT")) {
|
||||
message = "Is it a development build?";
|
||||
} else if (versionedResource.running() != null) {
|
||||
message = "Did you downgrade it? If so, clear cache (delete Tweaks724/storage/cache/updater)";
|
||||
}
|
||||
|
||||
DebugLogger.warning("This version of %s doesn't exist on SpigotMC. %s", pluginName, message);
|
||||
errors++;
|
||||
} else {
|
||||
if (!newResource.running().equals(newResource.latest())) {
|
||||
availableUpdates.add(newResource);
|
||||
|
@ -66,6 +74,10 @@ public class UpdateChecker extends BukkitRunnable {
|
|||
DebugLogger.severe("Unable to refresh %s: %s".formatted(pluginName, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
if (errors > 0) {
|
||||
DebugLogger.info("To disable the updater for specific plugins, refer to updater_config.yml");
|
||||
}
|
||||
}
|
||||
|
||||
private void alert() {
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater.backend;
|
||||
package eu.m724.tweaks.module.updater.backend;
|
||||
|
||||
import eu.m724.tweaks.updater.object.ResourceVersion;
|
||||
import eu.m724.tweaks.module.updater.object.ResourceVersion;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
|
@ -4,17 +4,17 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater.backend;
|
||||
package eu.m724.tweaks.module.updater.backend;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.updater.object.SpigotResource;
|
||||
import eu.m724.tweaks.updater.object.ResourceVersion;
|
||||
import eu.m724.tweaks.updater.object.UpdateDescription;
|
||||
import eu.m724.tweaks.updater.object.VersionedResource;
|
||||
import eu.m724.tweaks.module.updater.object.SpigotResource;
|
||||
import eu.m724.tweaks.module.updater.object.ResourceVersion;
|
||||
import eu.m724.tweaks.module.updater.object.UpdateDescription;
|
||||
import eu.m724.tweaks.module.updater.object.VersionedResource;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
@ -43,7 +43,6 @@ public class VersionScanner extends CompletableFuture<VersionedResource> {
|
|||
}
|
||||
|
||||
private void start() {
|
||||
//System.out.printf("STarting for %d %s\n", resource.resourceId(), resource.plugin().getName());
|
||||
DebugLogger.finer("Scanning %s (#%d) from page %d", resource.name(), resource.resourceId(), fromPage);
|
||||
|
||||
try (ExecutorService executor = Executors.newSingleThreadExecutor()) {
|
||||
|
@ -54,7 +53,7 @@ public class VersionScanner extends CompletableFuture<VersionedResource> {
|
|||
|
||||
int page;
|
||||
for (page = fromPage; page < 1000; page++) {
|
||||
DebugLogger.finer("Scan %s now at page %d", resource.name(), fromPage);
|
||||
DebugLogger.finer("Scan %s now at page %d", resource.name(), page);
|
||||
|
||||
String url = "https://api.spigotmc.org/simple/0.2/index.php?action=getResourceUpdates&page=%d&id=%d".formatted(page, resource.resourceId());
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater.object;
|
||||
package eu.m724.tweaks.module.updater.object;
|
||||
|
||||
import java.util.Objects;
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater.object;
|
||||
package eu.m724.tweaks.module.updater.object;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater.object;
|
||||
package eu.m724.tweaks.module.updater.object;
|
||||
|
||||
public record UpdateDescription(
|
||||
String title,
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.updater.object;
|
||||
package eu.m724.tweaks.module.updater.object;
|
||||
|
||||
public record VersionedResource(
|
||||
SpigotResource resource,
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.wordcoords;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.module.wordcoords.converter.Decoder;
|
||||
import eu.m724.tweaks.module.wordcoords.converter.Encoder;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class WordCoordsConverter {
|
||||
private final Encoder encoder;
|
||||
private final Decoder decoder;
|
||||
|
||||
|
||||
public WordCoordsConverter(WordList wordList) {
|
||||
this.encoder = new Encoder(wordList);
|
||||
this.decoder = new Decoder(wordList);
|
||||
|
||||
DebugLogger.fine("Words: %d (%d bits)", wordList.getWordCount(), wordList.getBitsPerWord());
|
||||
DebugLogger.fine("Bits per word: %d", wordList.getBitsPerWord());
|
||||
}
|
||||
|
||||
public String[] encode(int x, int z) {
|
||||
return encoder.encode(x, z);
|
||||
}
|
||||
|
||||
public int[] decode(String[] words) throws NoSuchElementException {
|
||||
return decoder.decode(words);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.wordcoords;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class WordCoordsModule extends TweaksModule implements CommandExecutor, Listener {
|
||||
private WordList wordList;
|
||||
private WordCoordsConverter converter;
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
try {
|
||||
this.wordList = WordList.fromFile(getPlugin().getDataFolder().toPath().resolve("storage/wordlist.txt"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
this.converter = new WordCoordsConverter(wordList);
|
||||
|
||||
registerCommand("wordcoords", this);
|
||||
registerEvents(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
int x = 0, z = 0;
|
||||
String[] words = new String[0];
|
||||
|
||||
boolean encode = false; // means encode pos to words
|
||||
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Language.getString("wordCoordsPlayerOnly"));
|
||||
return true;
|
||||
}
|
||||
|
||||
x = player.getLocation().getBlockX();
|
||||
z = player.getLocation().getBlockZ();
|
||||
|
||||
encode = true;
|
||||
} else if (args.length > 1) {
|
||||
try {
|
||||
double dx = Double.parseDouble(args[0]);
|
||||
double dz = Double.parseDouble(args[args.length > 2 ? 2 : 1]);
|
||||
|
||||
if (dx > Integer.MAX_VALUE || dx < Integer.MIN_VALUE || dz > Integer.MAX_VALUE || dz < Integer.MIN_VALUE) {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsOutOfRange", ChatColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
x = (int) dx;
|
||||
z = (int) dz;
|
||||
|
||||
encode = true;
|
||||
} catch (NumberFormatException ignored) { }
|
||||
}
|
||||
|
||||
if (encode) {
|
||||
words = converter.encode(x, z);
|
||||
String encoded = "///" + String.join(".", words);
|
||||
|
||||
BaseComponent[] components = new ComponentBuilder()
|
||||
.append(String.format("%d, %d encodes to ", x, z))
|
||||
.color(ChatColor.GRAY)
|
||||
.append(encoded)
|
||||
.color(ChatColor.AQUA) // TODO improve color
|
||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, encoded))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy")))
|
||||
.create();
|
||||
|
||||
sender.spigot().sendMessage(components);
|
||||
} else {
|
||||
String strArgs = String.join(" ", args);
|
||||
words = smartDetectWords(strArgs);
|
||||
|
||||
if (words.length == 0) {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsNoWords", ChatColor.GRAY));
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
int[] xz = converter.decode(words);
|
||||
x = xz[0];
|
||||
z = xz[1];
|
||||
} catch (NoSuchElementException e) {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsInvalidWord", ChatColor.RED, e.getMessage()));
|
||||
return true;
|
||||
}
|
||||
|
||||
String encoded = "///" + String.join(".", words);
|
||||
|
||||
BaseComponent[] components = new ComponentBuilder()
|
||||
.append(encoded + " decodes to ")
|
||||
.color(ChatColor.GRAY)
|
||||
.append("%d, %d".formatted(x, z))
|
||||
.color(ChatColor.AQUA) // TODO improve color
|
||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "%d, %d".formatted(x, z)))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy")))
|
||||
.append(" ±8")
|
||||
.color(ChatColor.GRAY)
|
||||
.create();
|
||||
sender.spigot().sendMessage(components);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String[] smartDetectWords(String str) {
|
||||
List<String> words = new ArrayList<>();
|
||||
StringBuilder currentWord = new StringBuilder();
|
||||
|
||||
for (int i=0; i<str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
|
||||
if (Character.isLetter(c)) {
|
||||
currentWord.append(c);
|
||||
} else {
|
||||
if (!currentWord.isEmpty()) {
|
||||
words.add(currentWord.toString());
|
||||
currentWord.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentWord.isEmpty()) {
|
||||
words.add(currentWord.toString());
|
||||
}
|
||||
|
||||
return words.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent event) {
|
||||
if (event.getMessage().startsWith("///")) {
|
||||
event.setCancelled(true);
|
||||
|
||||
event.getPlayer().performCommand("wordcoords " + event.getMessage().substring(3));
|
||||
}
|
||||
}
|
||||
}
|
63
src/main/java/eu/m724/tweaks/module/wordcoords/WordList.java
Normal file
63
src/main/java/eu/m724/tweaks/module/wordcoords/WordList.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.wordcoords;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class WordList {
|
||||
private final List<String> wordList;
|
||||
private final int bitsPerWord;
|
||||
|
||||
public WordList(List<String> words) {
|
||||
this.wordList = words;
|
||||
this.bitsPerWord = 32 - Integer.numberOfLeadingZeros(words.size()) - 1;
|
||||
}
|
||||
|
||||
public String getWord(int index) {
|
||||
return wordList.get(index);
|
||||
}
|
||||
|
||||
public int getWordIndex(String word) {
|
||||
return wordList.indexOf(word);
|
||||
}
|
||||
|
||||
public String[] getWords(int... indexes) {
|
||||
return Arrays.stream(indexes)
|
||||
.mapToObj(this::getWord)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public int[] getIndexes(String... words) {
|
||||
return Arrays.stream(words)
|
||||
.mapToInt(wordList::indexOf)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
public int getWordCount() {
|
||||
return wordList.size();
|
||||
}
|
||||
|
||||
public int getBitsPerWord() {
|
||||
return bitsPerWord;
|
||||
}
|
||||
|
||||
public static WordList fromFile(Path path) throws IOException {
|
||||
try (var lines = Files.lines(path)) {
|
||||
var list = lines.filter(s -> !s.isBlank())
|
||||
.map(String::toLowerCase)
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
return new WordList(list);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.wordcoords.converter;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.module.wordcoords.WordList;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Decoder {
|
||||
private final WordList wordList;
|
||||
private final int bitsPerWord;
|
||||
|
||||
public Decoder(WordList wordList) {
|
||||
this.wordList = wordList;
|
||||
this.bitsPerWord = wordList.getBitsPerWord();
|
||||
}
|
||||
|
||||
public int[] decode(String[] words) throws NoSuchElementException {
|
||||
int[] wordIndexes = new int[words.length];
|
||||
|
||||
for (int i=0; i<words.length; i++) {
|
||||
wordIndexes[i] = wordList.getWordIndex(words[i]);
|
||||
if (wordIndexes[i] == -1)
|
||||
throw new NoSuchElementException(words[i]);
|
||||
}
|
||||
|
||||
return decode(wordIndexes);
|
||||
}
|
||||
|
||||
public int[] decode(int[] wordIndexes) {
|
||||
DebugLogger.finer("Decoding word indexes: %s", Arrays.toString(wordIndexes));
|
||||
|
||||
int bitsRequired = wordIndexes.length * wordList.getBitsPerWord();
|
||||
int bitsRequiredPerCoordinate = bitsRequired / 2;
|
||||
DebugLogger.finer("Bits required: %d (per coord: %d)", bitsRequired, bitsRequiredPerCoordinate);
|
||||
|
||||
|
||||
long combinedValue = wordIndexesToCombinedValue(wordIndexes);
|
||||
DebugLogger.finer("Combined value: %d", combinedValue);
|
||||
|
||||
|
||||
int[] decodedCoords = decodeCoords(combinedValue, bitsRequiredPerCoordinate);
|
||||
int chunkX = decodedCoords[0];
|
||||
int chunkZ = decodedCoords[1];
|
||||
DebugLogger.finer("Chunk: %d, %d", chunkX, chunkZ);
|
||||
|
||||
// +8 to make it center of chunk
|
||||
int xCoord = chunkX * 16 + 8;
|
||||
int zCoord = chunkZ * 16 + 8;
|
||||
DebugLogger.finer("Decoded to coordinates: %d, %d", xCoord, zCoord);
|
||||
|
||||
return new int[] { xCoord, zCoord };
|
||||
}
|
||||
|
||||
private long wordIndexesToCombinedValue(int[] wordIndexes) {
|
||||
long combinedValue = 0;
|
||||
|
||||
for (int i=0; i<wordIndexes.length; i++) {
|
||||
combinedValue <<= bitsPerWord;
|
||||
combinedValue |= wordIndexes[i];
|
||||
}
|
||||
|
||||
return combinedValue;
|
||||
}
|
||||
|
||||
private int[] decodeCoords(long combinedValue, int bitsRequiredPerCoordinate) {
|
||||
int coordinateMask = (1 << bitsRequiredPerCoordinate) - 1;
|
||||
int coordinateOffset = 1 << (bitsRequiredPerCoordinate - 1);
|
||||
|
||||
int z = (int) (combinedValue & coordinateMask) - coordinateOffset;
|
||||
int x = (int) (combinedValue >> bitsRequiredPerCoordinate) - coordinateOffset;
|
||||
|
||||
return new int[] { x, z };
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.module.wordcoords.converter;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.module.wordcoords.WordList;
|
||||
import java.util.Arrays;
|
||||
public class Encoder {
|
||||
private final WordList wordList;
|
||||
private final int bitsPerWord;
|
||||
|
||||
public Encoder(WordList wordList) {
|
||||
this.wordList = wordList;
|
||||
this.bitsPerWord = wordList.getBitsPerWord();
|
||||
}
|
||||
|
||||
public String[] encode(int xCoord, int zCoord) {
|
||||
int chunkX = Math.floorDiv(xCoord, 16);
|
||||
int chunkZ = Math.floorDiv(zCoord, 16);
|
||||
DebugLogger.finer("Chunk: %d, %d", chunkX, chunkZ);
|
||||
|
||||
// Calculate minimum bits required per coordinate based on range
|
||||
int bitsRequiredPerCoordinate = findBitsRequiredPerCoordinate(chunkX, chunkZ);
|
||||
int minTotalBits = bitsRequiredPerCoordinate * 2;
|
||||
DebugLogger.finer("Min bits required per coordinate: %d (total: %d)", bitsRequiredPerCoordinate, minTotalBits);
|
||||
|
||||
// Calculate words required, ensuring total bits is sufficient and even
|
||||
int wordsRequired = 0;
|
||||
int actualTotalBits = 0;
|
||||
if (minTotalBits > 0) { // Avoid division by zero if bitsPerWord is 0, or log(0)
|
||||
wordsRequired = Math.ceilDiv(minTotalBits, bitsPerWord);
|
||||
actualTotalBits = wordsRequired * bitsPerWord;
|
||||
// Ensure total bits is sufficient
|
||||
while (actualTotalBits < minTotalBits) {
|
||||
wordsRequired++;
|
||||
actualTotalBits = wordsRequired * bitsPerWord;
|
||||
}
|
||||
} // else: coords are 0 or -1, minTotalBits=0, wordsRequired=0, actualTotalBits=0. Need special handling?
|
||||
// If x/z are 0/-1, findBitsRequired returns 1, minTotalBits=2. The loop handles it.
|
||||
|
||||
// Final bits per coordinate based on words
|
||||
bitsRequiredPerCoordinate = actualTotalBits / 2;
|
||||
DebugLogger.finer("Final Words required: %d", wordsRequired);
|
||||
DebugLogger.finer("Final Bits required: %d (per coord: %d)", actualTotalBits, bitsRequiredPerCoordinate);
|
||||
|
||||
int encodedX = encodeCoord(chunkX, bitsRequiredPerCoordinate);
|
||||
int encodedZ = encodeCoord(chunkZ, bitsRequiredPerCoordinate);
|
||||
DebugLogger.finer("Encoded coordinates: %d, %d", encodedX, encodedZ);
|
||||
|
||||
long combinedValue = ((long) encodedX << bitsRequiredPerCoordinate) | encodedZ;
|
||||
DebugLogger.finer("Combined value: %d", combinedValue);
|
||||
|
||||
int[] wordIndexes = combinedValueToWordIndexes(combinedValue, wordsRequired);
|
||||
DebugLogger.finer("Word indexes: %s", Arrays.toString(wordIndexes));
|
||||
|
||||
return wordList.getWords(wordIndexes);
|
||||
}
|
||||
|
||||
// Calculates the minimum number of bits required to represent the coordinate
|
||||
// using the encoding scheme (offset + coord) & mask, such that the coordinate
|
||||
// fits within the range [-(1 << (bits - 1)), (1 << (bits - 1)) - 1].
|
||||
private int findBitsRequiredPerCoordinate(int x, int z) {
|
||||
int maxVal = Math.max(x, z);
|
||||
int minVal = Math.min(x, z);
|
||||
|
||||
// Determine the required positive magnitude for the encoding range's positive side.
|
||||
// We need `(1 << (bits - 1)) >= max(maxVal + 1, -minVal)`
|
||||
int requiredPositiveMagnitude = Math.max(maxVal + 1, -minVal);
|
||||
|
||||
if (requiredPositiveMagnitude <= 0) {
|
||||
// Occurs only if maxVal <= -1 and minVal >= 0, which is impossible,
|
||||
// OR maxVal <= 0 and -minVal <= 0 => maxVal <= 0 and minVal >= 0.
|
||||
// This means x and z are both 0.
|
||||
// The range for 1 bit is [-1, 0]. If coords are 0, 1 bit is not enough for offset+coord.
|
||||
// Example: bits=1. offset=1<<0=1. mask=(1<<1)-1=1.
|
||||
// encodeCoord(0, 1) = (1+0)&1 = 1.
|
||||
// decodeCoord(1, 1): val=1. mask=1. offset=1. (1&1)-1 = 0. Correct.
|
||||
// What if we need to represent -1? encodeCoord(-1, 1) = (1-1)&1 = 0.
|
||||
// decodeCoord(0, 1): val=0. (0&1)-1 = -1. Correct.
|
||||
// So 1 bit works for range [-1, 0]. Let's check the condition:
|
||||
// x=0, z=0 -> maxVal=0, minVal=0. reqPosMag = max(1, 0) = 1.
|
||||
// x=-1, z=-1 -> maxVal=-1, minVal=-1. reqPosMag = max(0, 1) = 1.
|
||||
// x=0, z=-1 -> maxVal=0, minVal=-1. reqPosMag = max(1, 1) = 1.
|
||||
// So requiredPositiveMagnitude is 1 for the range [-1, 0].
|
||||
requiredPositiveMagnitude = 1; // Ensure it's at least 1 if coords are 0 or -1.
|
||||
}
|
||||
|
||||
// Calculate p = bits - 1
|
||||
// We need the smallest integer p such that (1 << p) >= requiredPositiveMagnitude.
|
||||
// If requiredPositiveMagnitude = 1, we need 1 << p >= 1, smallest p is 0.
|
||||
// If requiredPositiveMagnitude > 1, this is equivalent to finding the number of bits
|
||||
// needed to represent (requiredPositiveMagnitude - 1) in binary.
|
||||
int p;
|
||||
if (requiredPositiveMagnitude == 1) {
|
||||
p = 0;
|
||||
} else {
|
||||
p = 32 - Integer.numberOfLeadingZeros(requiredPositiveMagnitude - 1);
|
||||
}
|
||||
|
||||
// bits = p + 1
|
||||
return p + 1;
|
||||
}
|
||||
|
||||
private int encodeCoord(int coord, int bitsRequiredPerCoordinate) {
|
||||
// Bitmask and offset for positive integer conversion
|
||||
int coordinateMask = (1 << bitsRequiredPerCoordinate) - 1;
|
||||
int coordinateOffset = 1 << (bitsRequiredPerCoordinate - 1);
|
||||
|
||||
// Encode coordinates with offset into positive range
|
||||
return (coordinateOffset + coord) & coordinateMask;
|
||||
}
|
||||
|
||||
private int[] combinedValueToWordIndexes(long combinedValue, int wordsRequired) {
|
||||
int bitsRequired = wordsRequired * bitsPerWord;
|
||||
|
||||
// Break into word indexes
|
||||
int[] wordIndexes = new int[wordsRequired];
|
||||
int currentIndex = wordsRequired; // Start filling from end of array
|
||||
|
||||
for (int remainingBits = bitsRequired; remainingBits > 0; remainingBits -= bitsPerWord) {
|
||||
int wordMask = (1 << bitsPerWord) - 1;
|
||||
wordIndexes[--currentIndex] = (int) (combinedValue & wordMask);
|
||||
combinedValue >>= bitsPerWord;
|
||||
}
|
||||
|
||||
return wordIndexes;
|
||||
}
|
||||
}
|
|
@ -4,9 +4,9 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.worldborder;
|
||||
package eu.m724.tweaks.module.worldborder;
|
||||
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.CraftWorld;
|
||||
import org.bukkit.event.EventHandler;
|
|
@ -4,11 +4,11 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.worldborder;
|
||||
package eu.m724.tweaks.module.worldborder;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import eu.m724.tweaks.TweaksModule;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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.sleep;
|
||||
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
public class TimeForwardRunnable extends BukkitRunnable {
|
||||
private final Server server;
|
||||
private final World world; // TODO multi worlds
|
||||
|
||||
private final double percentage;
|
||||
|
||||
public TimeForwardRunnable(Plugin plugin) {
|
||||
this.server = plugin.getServer();
|
||||
this.world = server.getWorld("world");
|
||||
this.percentage = (world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE) / 100.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
int playersSleeping = SleepState.playersSleeping;
|
||||
//System.out.println(playersSleeping);
|
||||
if (playersSleeping == 0) return;
|
||||
|
||||
int onlinePlayers = (int) (server.getOnlinePlayers().size() / percentage); // TODO optimize remove size every tick maybe
|
||||
|
||||
double sleepPercentage = (double) playersSleeping / onlinePlayers;
|
||||
|
||||
// we want sleep to take 200 ticks which is 10 seconds assuming all palyres onilien
|
||||
|
||||
long time = world.getTime();
|
||||
long untilDay = 23459 - time;
|
||||
|
||||
if (untilDay == 0) return;
|
||||
|
||||
long perSkip = 200 + (100000 / -untilDay);
|
||||
perSkip = Math.clamp(perSkip, 20, 200);
|
||||
perSkip = (long) (perSkip * sleepPercentage);
|
||||
|
||||
world.setTime(world.getTime() + perSkip);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
# - https://discord.gg/86X4Z5JUeq
|
||||
# - https://www.spigotmc.org/threads/tweaks724.670906/
|
||||
|
||||
# Metrics toggle. Ideally opt-in, but the system is very new and it needs testing.
|
||||
# Metrics toggle. Please keep this on.
|
||||
metrics: true
|
||||
|
||||
# Warning: Don't use /worldborder while this is on
|
||||
|
@ -36,9 +36,9 @@ doors:
|
|||
knocking: true
|
||||
|
||||
motd:
|
||||
enabled: true
|
||||
# Name of the set containing the MOTDs
|
||||
# (random displayed every ping)
|
||||
# "" or false to disable
|
||||
set: "example"
|
||||
|
||||
chat:
|
||||
|
@ -81,8 +81,7 @@ hardcore:
|
|||
# 0.0 - 1.0 decimal. This is if you want to make it like an Easter egg
|
||||
chance: 1.0
|
||||
|
||||
# Makes sleeping
|
||||
# And adds a nice animation
|
||||
# Sleep tweaks
|
||||
# Percentage: playersSleepingPercentage gamerule
|
||||
# If instant: how much % of players to skip the night
|
||||
# If not: how much % make skipping full speed
|
||||
|
|
|
@ -8,7 +8,7 @@ api-version: 1.21.1
|
|||
softdepend: [ProtocolLib]
|
||||
|
||||
libraries:
|
||||
- eu.m724:mstats-spigot:0.1.0
|
||||
- eu.m724:mstats-spigot:0.1.2
|
||||
|
||||
commands:
|
||||
chat:
|
||||
|
@ -40,25 +40,36 @@ commands:
|
|||
servkill:
|
||||
description: Immediately stop the server
|
||||
permission: tweaks724.servkill
|
||||
durabilityalert:
|
||||
description: Durability alert toggle
|
||||
permission: tweaks724.durabilityalert
|
||||
wordcoords:
|
||||
description: Word to coords conversion
|
||||
permission: tweaks724.wordcoords
|
||||
aliases: [woco, wc, w3w]
|
||||
|
||||
permissions:
|
||||
tweaks724.chatmanage:
|
||||
default: true
|
||||
tweaks724.pomodoro:
|
||||
default: true
|
||||
tweaks724.updates:
|
||||
default: op
|
||||
tweaks724.tauth:
|
||||
default: op
|
||||
tweaks724.bypass-full:
|
||||
default: op
|
||||
tweaks724.emergencyalert:
|
||||
default: op
|
||||
tweaks724.retstone:
|
||||
default: op
|
||||
tweaks724.servkill:
|
||||
default: false
|
||||
tweaks724.durabilityalert:
|
||||
default: true
|
||||
tweaks724.wordcoords:
|
||||
default: true
|
||||
|
||||
7weaks724.ignore.this:
|
||||
description: "Internal, not for use. ${project.spigot.version}"
|
||||
default: false
|
||||
tweaks724:
|
||||
chatmanage:
|
||||
default: true
|
||||
pomodoro:
|
||||
default: true
|
||||
updates:
|
||||
default: op
|
||||
tauth:
|
||||
default: op
|
||||
bypass-full:
|
||||
default: op
|
||||
emergencyalert:
|
||||
default: op
|
||||
retstone:
|
||||
default: op
|
||||
servkill:
|
||||
default: false
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (C) 2024 Minecon724
|
||||
# Copyright (C) 2025 Minecon724
|
||||
# Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
# in the project root for the full license text.
|
||||
#
|
||||
|
@ -13,23 +13,35 @@ languageEnglish = English
|
|||
updateAvailableNotice = Available updates (%d):
|
||||
|
||||
# Used in /updates
|
||||
updatesNotChecked = Not checked yet
|
||||
updatesNotChecked = Not checked yet.
|
||||
# %s is time as HH:mm
|
||||
updatesNoUpdates = No available updates. Last checked: %s
|
||||
# %s is update title
|
||||
updatesClickToOpen = Click to open on SpigotMC "%s"
|
||||
|
||||
# Used in /chat
|
||||
chatPasswordProtected = This room is password protected
|
||||
chatWrongPassword = Wrong password
|
||||
chatNoSuchRoom = No room named %s
|
||||
chatAlreadyHere = You're already in this room
|
||||
chatPasswordProtected = This room is password protected.
|
||||
chatWrongPassword = Wrong password.
|
||||
chatNoSuchRoom = Room %s doesn't exist.
|
||||
chatNoSuchRoomInvalidId = Room %s doesn't exist, because the ID is invalid.
|
||||
chatAlreadyHere = You're already in this room.
|
||||
|
||||
# Used when a player joins using the wrong key or no key
|
||||
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!
|
||||
authKickError = An error occured. Please try again. If this persists, contact an administrator.
|
||||
|
||||
redstoneGatewayItem = Redstone gateway
|
||||
|
||||
clickToCopy = Click to copy to clipboard
|
||||
clickToCopy = Click to copy to clipboard
|
||||
clickToExecuteCommand = Click to execute command
|
||||
|
||||
durabilityEnabled = Enabled durability alert
|
||||
durabilityDisabled = Disabled durability alert
|
||||
|
||||
# When console executes /wordcoords without arguments
|
||||
wordCoordsPlayerOnly = Only players can execute this command without arguments.
|
||||
wordCoordsOutOfRange = Those coordinates are invalid.
|
||||
wordCoordsInvalidWord = Invalid word: "%s"
|
||||
wordCoordsNoWords = Please provide the Z coordinate.
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright (C) 2024 Minecon724
|
||||
# Copyright (C) 2025 Minecon724
|
||||
# Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
# in the project root for the full license text.
|
||||
#
|
||||
|
|
Loading…
Add table
Reference in a new issue