Compare commits

...

32 commits

Author SHA1 Message Date
4f752bef61
Quick mode
Some checks failed
/ build (push) Failing after 44s
2025-04-06 12:05:28 +02:00
41583939ac
Use component 2025-04-06 11:29:50 +02:00
22bb2d16d2
Fix encoding and decoding 2025-04-06 10:25:18 +02:00
Minecon724
598902ef33
update readme
Some checks failed
/ build (push) Failing after 7s
Signed-off-by: Minecon724 <git@m724.eu>
2025-02-19 11:24:22 +01:00
Minecon724
7cd334f4a2
feat: Initial word coords module
Some checks failed
/ build (push) Failing after 7s
Signed-off-by: Minecon724 <git@m724.eu>
2025-02-18 14:09:19 +01:00
Minecon724
b421b48e51
chore: Update mStats to fix am issue
Some checks failed
/ build (push) Failing after 6s
Signed-off-by: Minecon724 <git@m724.eu>
2025-02-17 09:06:39 +01:00
Minecon724
684e31bf07
fix: correct string formatting in debug logger
Some checks failed
/ build (push) Failing after 6s
Updated the debug logger to use proper string formatting syntax. This ensures compatibility and avoids runtime errors.

Signed-off-by: Minecon724 <git@m724.eu>
2025-02-04 17:20:45 +01:00
Minecon724
cf654fcb42
[maven-release-plugin] prepare for next development iteration
Some checks failed
/ build (push) Has been cancelled
2025-02-03 11:27:37 +01:00
Minecon724
0c4690e2e7
[maven-release-plugin] prepare release tweaks-0.1.14
Some checks failed
/ build (push) Has been cancelled
2025-02-03 11:27:34 +01:00
Minecon724
05b9ca4cb8
feat(chat): initialize ChatRoomLoader on plugin start
Some checks are pending
/ build (push) Waiting to run
- Added ChatRoomLoader initialization during ChatModule setup.
- Ensures necessary resources are loaded before chat operations.

Signed-off-by: Minecon724 <git@m724.eu>
2025-02-03 11:27:21 +01:00
Minecon724
316d479fd8
feat(chat): improve chat room management UX
- Added hover and click actions to chat commands for better interaction.
- Enhanced error and confirmation messages for clarity.
- Introduced validation for room IDs and improved handling of invalid IDs.

Signed-off-by: Minecon724 <git@m724.eu>
2025-02-03 11:25:26 +01:00
Minecon724
6ff6ec9d6b
fix: improve error handling in ChatCommands
Updated error handling to throw RuntimeException instead of printing stack traces. Enhanced user feedback by standardizing error messages across various chat command operations.

Signed-off-by: Minecon724 <git@m724.eu>
2025-02-03 10:52:04 +01:00
Minecon724
541f095075
fix(motd): handle FileAlreadyExistsException in directory creation
Added a catch block for FileAlreadyExistsException to prevent unnecessary exceptions when the "motd sets" directory already exists. This ensures smoother execution and improves error handling during initialization.

Signed-off-by: Minecon724 <git@m724.eu>
2025-02-03 10:42:12 +01:00
Minecon724
1fafc90e04
refactor(pomodoro): simplify plugin instance retrieval
Some checks are pending
/ build (push) Waiting to run
Replaced passing the plugin instance to PomodoroRunnable with direct access via TweaksPlugin singleton. This removes redundant code, improving clarity and maintainability.
2025-02-02 14:57:18 +01:00
Minecon724
ad1a699d38
feat(auth): improve error handling and key generation logic
Added new error handling for key assignment failures, including user-facing messaging. Enhanced secure key generation with fixed length and randomness via `SecureRandom`. Removed redundant TODO comments and replaced placeholder exception handling with actionable implementations.
2025-02-02 14:57:00 +01:00
Minecon724
7de46b879b
Refactor MOTD configuration and improve file handling
Revised the MOTD configuration setup to use a direct boolean flag for enabling/disabling instead of relying on string checks. Updated file handling to use `Path` and modernized directory creation logic for better error handling and clarity. Removed unused code and cleaned up resource management in `MotdModule`.
2025-02-02 14:12:19 +01:00
Minecon724
cbe9f77cca
Remove obsolete comment about static method in TweaksModule
The removed comment questioned making a method non-static, which is already resolved or irrelevant. This cleanup improves code readability and eliminates confusion.
2025-02-02 14:05:44 +01:00
Minecon724
c096674a15
Support sleep mechanics across multiple worlds
Refactored `TimeForwardRunnable` to handle multiple worlds instead of a single hardcoded world. The code now dynamically retrieves sleep percentage and processes time advancement for each world, improving flexibility and compatibility.
2025-02-02 14:03:22 +01:00
Minecon724
232e0bfa9a
Durability toggle
All checks were successful
/ build (push) Successful in 44s
2025-01-28 10:18:15 +01:00
Minecon724
9345efe1d4
Initial durability module
All checks were successful
/ build (push) Successful in 50s
2025-01-27 08:45:52 +01:00
Minecon724
2761ed8757
Slightly smarter updater notice
All checks were successful
/ build (push) Successful in 50s
2025-01-26 12:41:40 +01:00
Minecon724
2a65e9dbcb
Fix small debug log mistake
All checks were successful
/ build (push) Successful in 46s
2025-01-25 20:32:17 +01:00
Minecon724
29f79a8771
Improve config loader
All checks were successful
/ build (push) Successful in 50s
2025-01-24 18:42:39 +01:00
Minecon724
7b65be86ad
Move modules to modules 2025-01-24 10:05:49 +01:00
Minecon724
2788829967
[maven-release-plugin] prepare for next development iteration
All checks were successful
/ build (push) Successful in 47s
2025-01-20 13:54:42 +01:00
Minecon724
cbfbdf8ce3
[maven-release-plugin] prepare release tweaks-0.1.13
All checks were successful
/ build (push) Successful in 49s
2025-01-20 13:54:41 +01:00
Minecon724
1ac32a093f
Use boring syntax for permissions
Some checks failed
/ build (push) Has been cancelled
2025-01-20 13:54:19 +01:00
Minecon724
15c97ee256
Ask to keep metrics
All checks were successful
/ build (push) Successful in 52s
2025-01-20 12:50:54 +01:00
Minecon724
7ed9a702ca
Replace nonsensical text
All checks were successful
/ build (push) Successful in 47s
2025-01-20 12:49:37 +01:00
Minecon724
bcd6827c29
Fix workflow and make it on Alpine
All checks were successful
/ build (push) Successful in 44s
2025-01-20 12:12:42 +01:00
Minecon724
2890f00acd
Improve build guide 2025-01-20 12:12:12 +01:00
Minecon724
f43b17078e
[maven-release-plugin] prepare for next development iteration
Some checks failed
/ build (push) Failing after 39s
2025-01-20 11:21:57 +01:00
85 changed files with 1678 additions and 664 deletions

View file

@ -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

View file

@ -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> \

View file

@ -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
View file

@ -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>

View file

@ -6,6 +6,8 @@
package eu.m724.tweaks;
import eu.m724.tweaks.module.TweaksModule;
import java.util.logging.Level;
import java.util.logging.Logger;

View file

@ -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;
}
}

View file

@ -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() {

View file

@ -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;
}
}

View 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);
}
}

View file

@ -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);
}
}

View 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())
);
}
}
}

View file

@ -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();
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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
}
}

View file

@ -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

View file

@ -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 { }
}

View 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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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());
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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.*;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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() {

View file

@ -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;

View file

@ -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());

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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));
}
}
}

View 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);
}
}
}

View file

@ -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 };
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.
#