feat(rules-mod): Add Firefrost Rules Mod source — all 3 versions
NeoForge 1.21.1, Forge 1.20.1, Forge 1.16.5 All compiled and deployed to NextCloud (Task #136) Source committed for Task #138 (CurseForge generic fork) CLAUDE.md with build environment docs included Claude (Chronicler #83 - The Compiler) <claude@firefrostgaming.com>
This commit is contained in:
parent
240a4776f6
commit
179bac2911
45
services/rules-mod/1.16.5/build.gradle
Normal file
45
services/rules-mod/1.16.5/build.gradle
Normal file
@@ -0,0 +1,45 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle'
|
||||
|
||||
version = '1.0.0'
|
||||
group = 'com.firefrostgaming.rules'
|
||||
archivesBaseName = 'firefrostrules'
|
||||
|
||||
java.toolchain.languageVersion = JavaLanguageVersion.of(8)
|
||||
|
||||
minecraft {
|
||||
mappings channel: 'official', version: '1.16.5'
|
||||
runs {
|
||||
server {
|
||||
workingDirectory project.file('run')
|
||||
mods {
|
||||
firefrostrules {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft 'net.minecraftforge:forge:1.16.5-36.2.39'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(["Implementation-Title": "Firefrost Rules", "Implementation-Version": project.version])
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
2
services/rules-mod/1.16.5/gradle.properties
Normal file
2
services/rules-mod/1.16.5/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.daemon=false
|
||||
7
services/rules-mod/1.16.5/settings.gradle
Normal file
7
services/rules-mod/1.16.5/settings.gradle
Normal file
@@ -0,0 +1,7 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
}
|
||||
}
|
||||
rootProject.name = 'firefrostrules'
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class CooldownManager {
|
||||
private static final ConcurrentHashMap<UUID, Instant> COOLDOWNS = new ConcurrentHashMap<>();
|
||||
|
||||
public static boolean checkAndUpdateCooldown(ServerPlayerEntity player) {
|
||||
UUID playerId = player.getUUID();
|
||||
Instant now = Instant.now();
|
||||
int cooldownSeconds = ServerRulesConfig.COOLDOWN_SECONDS.get();
|
||||
Instant lastUsed = COOLDOWNS.get(playerId);
|
||||
if (lastUsed != null) {
|
||||
long secondsSinceLastUse = Duration.between(lastUsed, now).getSeconds();
|
||||
if (secondsSinceLastUse < cooldownSeconds) {
|
||||
long remaining = cooldownSeconds - secondsSinceLastUse;
|
||||
player.sendMessage(new StringTextComponent("\u00A7cPlease wait " + remaining + " seconds before checking the rules again."), player.getUUID());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
COOLDOWNS.put(playerId, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void removePlayer(UUID playerId) { COOLDOWNS.remove(playerId); }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordFetcher {
|
||||
private static final Logger LOGGER = LogManager.getLogger(DiscordFetcher.class);
|
||||
|
||||
public static CompletableFuture<String> fetchRulesAsync() {
|
||||
if (!ServerRulesConfig.isMessageIdValid()) {
|
||||
LOGGER.error("Invalid Discord Message ID in config.");
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
String token = ServerRulesConfig.BOT_TOKEN.get();
|
||||
String channelId = ServerRulesConfig.CHANNEL_ID.get();
|
||||
String messageId = ServerRulesConfig.MESSAGE_ID.get();
|
||||
URL url = new URL("https://discord.com/api/v10/channels/" + channelId + "/messages/" + messageId);
|
||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||
con.setRequestMethod("GET");
|
||||
con.setRequestProperty("Authorization", "Bot " + token);
|
||||
con.setRequestProperty("Accept", "application/json");
|
||||
con.setConnectTimeout(10000);
|
||||
con.setReadTimeout(10000);
|
||||
int status = con.getResponseCode();
|
||||
if (status == 200) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
|
||||
StringBuilder content = new StringBuilder();
|
||||
String inputLine;
|
||||
while ((inputLine = in.readLine()) != null) { content.append(inputLine); }
|
||||
in.close();
|
||||
JsonObject json = new JsonParser().parse(content.toString()).getAsJsonObject();
|
||||
return json.get("content").getAsString();
|
||||
} else {
|
||||
LOGGER.error("Discord API returned status: {}", status);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOGGER.error("Network error while fetching Discord rules", ex);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraft.util.text.IFormattableTextComponent;
|
||||
|
||||
public class DiscordFormatter {
|
||||
public static IFormattableTextComponent formatRules(String rawDiscordText) {
|
||||
String processedText = convertEmojis(rawDiscordText);
|
||||
String lowerText = processedText.toLowerCase();
|
||||
TextFormatting headerColor = TextFormatting.DARK_PURPLE;
|
||||
TextFormatting bodyColor = TextFormatting.LIGHT_PURPLE;
|
||||
if (lowerText.contains("fire") || lowerText.contains("[fire]")) {
|
||||
headerColor = TextFormatting.GOLD;
|
||||
bodyColor = TextFormatting.YELLOW;
|
||||
} else if (lowerText.contains("frost") || lowerText.contains("[frost]")) {
|
||||
headerColor = TextFormatting.AQUA;
|
||||
bodyColor = TextFormatting.DARK_AQUA;
|
||||
}
|
||||
IFormattableTextComponent rootComponent = new StringTextComponent("");
|
||||
String[] lines = processedText.split("\n");
|
||||
for (String line : lines) {
|
||||
IFormattableTextComponent lineComponent;
|
||||
if (line.startsWith("**") && line.endsWith("**")) {
|
||||
String cleanLine = line.replace("**", "");
|
||||
lineComponent = new StringTextComponent(cleanLine);
|
||||
lineComponent.withStyle(headerColor, TextFormatting.BOLD);
|
||||
} else if (line.trim().startsWith("-") || line.trim().startsWith("\u2022")) {
|
||||
lineComponent = new StringTextComponent(" " + line.trim());
|
||||
lineComponent.withStyle(bodyColor);
|
||||
} else {
|
||||
lineComponent = new StringTextComponent(line);
|
||||
lineComponent.withStyle(bodyColor);
|
||||
}
|
||||
rootComponent.append(lineComponent).append(new StringTextComponent("\n"));
|
||||
}
|
||||
return rootComponent;
|
||||
}
|
||||
|
||||
private static String convertEmojis(String text) {
|
||||
if (text == null) return "";
|
||||
return text.replace("\uD83D\uDD25", "[Fire]").replace("\u2744\uFE0F", "[Frost]")
|
||||
.replace("\uD83D\uDC9C", "[Arcane]");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class RulesCache {
|
||||
private static String cachedRules = null;
|
||||
private static Instant lastFetchTime = Instant.MIN;
|
||||
private static final String FALLBACK_RULES =
|
||||
"[Fire] Server Rules\n1. Be respectful to all players.\n2. No griefing or cheating.\n3. Follow staff instructions.\nPlease check Discord for the full rules list.";
|
||||
|
||||
public static boolean isCacheValid() {
|
||||
if (cachedRules == null) return false;
|
||||
long cacheMinutes = ServerRulesConfig.CACHE_MINUTES.get();
|
||||
return Instant.now().isBefore(lastFetchTime.plusSeconds(cacheMinutes * 60));
|
||||
}
|
||||
|
||||
public static void updateCache(String newRules) {
|
||||
if (newRules != null && !newRules.trim().isEmpty()) {
|
||||
cachedRules = newRules;
|
||||
lastFetchTime = Instant.now();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRules() { return cachedRules != null ? cachedRules : FALLBACK_RULES; }
|
||||
public static void invalidate() { cachedRules = null; lastFetchTime = Instant.MIN; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.command.CommandSource;
|
||||
import net.minecraft.command.Commands;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.util.text.IFormattableTextComponent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class RulesCommand {
|
||||
private static final Logger LOGGER = LogManager.getLogger(RulesCommand.class);
|
||||
|
||||
public static void register(CommandDispatcher<CommandSource> dispatcher) {
|
||||
dispatcher.register(Commands.literal("rules").executes(context -> {
|
||||
CommandSource source = context.getSource();
|
||||
if (!(source.getEntity() instanceof ServerPlayerEntity)) {
|
||||
source.sendSuccess(DiscordFormatter.formatRules(RulesCache.getRules()), false);
|
||||
return 1;
|
||||
}
|
||||
ServerPlayerEntity player = (ServerPlayerEntity) source.getEntity();
|
||||
if (!CooldownManager.checkAndUpdateCooldown(player)) return 0;
|
||||
if (RulesCache.isCacheValid()) {
|
||||
player.sendMessage(DiscordFormatter.formatRules(RulesCache.getRules()), player.getUUID());
|
||||
return 1;
|
||||
}
|
||||
player.sendMessage(new StringTextComponent("\u00A77Fetching latest rules..."), player.getUUID());
|
||||
DiscordFetcher.fetchRulesAsync().thenAccept(fetchedRules -> {
|
||||
String rulesText;
|
||||
if (fetchedRules != null) {
|
||||
RulesCache.updateCache(fetchedRules);
|
||||
rulesText = fetchedRules;
|
||||
} else {
|
||||
LOGGER.warn("Discord fetch failed. Falling back to cache for {}", player.getName().getString());
|
||||
rulesText = RulesCache.getRules();
|
||||
}
|
||||
IFormattableTextComponent formattedRules = DiscordFormatter.formatRules(rulesText);
|
||||
source.getServer().execute(() -> player.sendMessage(formattedRules, player.getUUID()));
|
||||
});
|
||||
return 1;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Mod("firefrostrules")
|
||||
public class ServerRules {
|
||||
private static final Logger LOGGER = LogManager.getLogger(ServerRules.class);
|
||||
|
||||
public ServerRules() {
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ServerRulesConfig.SPEC);
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
LOGGER.info("Firefrost Rules Mod Initialized.");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
RulesCommand.register(event.getDispatcher());
|
||||
LOGGER.info("Registered /rules command.");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
CooldownManager.removePlayer(event.getEntity().getUUID());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class ServerRulesConfig {
|
||||
public static final ForgeConfigSpec SPEC;
|
||||
public static final ForgeConfigSpec.ConfigValue<String> BOT_TOKEN;
|
||||
public static final ForgeConfigSpec.ConfigValue<String> CHANNEL_ID;
|
||||
public static final ForgeConfigSpec.ConfigValue<String> MESSAGE_ID;
|
||||
public static final ForgeConfigSpec.IntValue COOLDOWN_SECONDS;
|
||||
public static final ForgeConfigSpec.IntValue CACHE_MINUTES;
|
||||
|
||||
static {
|
||||
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
|
||||
builder.push("discord");
|
||||
BOT_TOKEN = builder.comment("Discord Bot Token").define("bot_token", "YOUR_TOKEN_HERE");
|
||||
CHANNEL_ID = builder.comment("Discord Channel ID").define("channel_id", "1234567890123456789");
|
||||
MESSAGE_ID = builder.comment("Discord Message ID").define("message_id", "1234567890123456789");
|
||||
builder.pop();
|
||||
builder.push("performance");
|
||||
COOLDOWN_SECONDS = builder.comment("Per-player cooldown in seconds").defineInRange("cooldown_seconds", 60, 0, 3600);
|
||||
CACHE_MINUTES = builder.comment("Cache duration in minutes").defineInRange("cache_minutes", 30, 1, 1440);
|
||||
builder.pop();
|
||||
SPEC = builder.build();
|
||||
}
|
||||
|
||||
public static boolean isMessageIdValid() {
|
||||
String id = MESSAGE_ID.get();
|
||||
return StringUtils.isNotBlank(id) && id.matches("^\\d{17,20}$");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
modLoader="javafml"
|
||||
loaderVersion="[36,)"
|
||||
license="All Rights Reserved"
|
||||
issueTrackerURL="https://firefrostgaming.com/support"
|
||||
|
||||
[[mods]]
|
||||
modId="firefrostrules"
|
||||
version="${file.jarVersion}"
|
||||
displayName="Firefrost Rules"
|
||||
displayURL="https://firefrostgaming.com"
|
||||
authors="Firefrost Gaming"
|
||||
description='''Fetches server rules dynamically from Discord for the /rules command.'''
|
||||
|
||||
[[dependencies.firefrostrules]]
|
||||
modId="forge"
|
||||
mandatory=true
|
||||
versionRange="[36,)"
|
||||
ordering="NONE"
|
||||
side="SERVER"
|
||||
|
||||
[[dependencies.firefrostrules]]
|
||||
modId="minecraft"
|
||||
mandatory=true
|
||||
versionRange="[1.16.5,1.17)"
|
||||
ordering="NONE"
|
||||
side="SERVER"
|
||||
50
services/rules-mod/1.20.1/build.gradle
Executable file
50
services/rules-mod/1.20.1/build.gradle
Executable file
@@ -0,0 +1,50 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '6.0.+'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle'
|
||||
|
||||
version = '1.0.0'
|
||||
group = 'com.firefrostgaming.rules'
|
||||
archivesBaseName = 'firefrostrules'
|
||||
|
||||
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||
|
||||
minecraft {
|
||||
mappings channel: 'official', version: '1.20.1'
|
||||
runs {
|
||||
server {
|
||||
workingDirectory project.file('run')
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
mods {
|
||||
firefrostrules {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft 'net.minecraftforge:forge:1.20.1-47.3.0'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes([
|
||||
"Implementation-Title": "Firefrost Rules",
|
||||
"Implementation-Version": project.version,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
2
services/rules-mod/1.20.1/gradle.properties
Executable file
2
services/rules-mod/1.20.1/gradle.properties
Executable file
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.daemon=false
|
||||
8
services/rules-mod/1.20.1/settings.gradle
Executable file
8
services/rules-mod/1.20.1/settings.gradle
Executable file
@@ -0,0 +1,8 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'firefrostrules'
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class CooldownManager {
|
||||
private static final ConcurrentHashMap<UUID, Instant> COOLDOWNS = new ConcurrentHashMap<>();
|
||||
|
||||
public static boolean checkAndUpdateCooldown(ServerPlayer player) {
|
||||
UUID playerId = player.getUUID();
|
||||
Instant now = Instant.now();
|
||||
int cooldownSeconds = ServerRulesConfig.COOLDOWN_SECONDS.get();
|
||||
Instant lastUsed = COOLDOWNS.get(playerId);
|
||||
if (lastUsed != null) {
|
||||
long secondsSinceLastUse = Duration.between(lastUsed, now).getSeconds();
|
||||
if (secondsSinceLastUse < cooldownSeconds) {
|
||||
long remaining = cooldownSeconds - secondsSinceLastUse;
|
||||
player.sendSystemMessage(Component.literal("\u00A7cPlease wait " + remaining + " seconds before checking the rules again."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
COOLDOWNS.put(playerId, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void removePlayer(UUID playerId) { COOLDOWNS.remove(playerId); }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordFetcher {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DiscordFetcher.class);
|
||||
private static final HttpClient CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
||||
|
||||
public static CompletableFuture<String> fetchRulesAsync() {
|
||||
if (!ServerRulesConfig.isMessageIdValid()) {
|
||||
LOGGER.error("Invalid Discord Message ID in config.");
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
String token = ServerRulesConfig.BOT_TOKEN.get();
|
||||
String channelId = ServerRulesConfig.CHANNEL_ID.get();
|
||||
String messageId = ServerRulesConfig.MESSAGE_ID.get();
|
||||
URI uri = URI.create("https://discord.com/api/v10/channels/" + channelId + "/messages/" + messageId);
|
||||
HttpRequest request = HttpRequest.newBuilder().uri(uri)
|
||||
.header("Authorization", "Bot " + token)
|
||||
.header("Accept", "application/json").GET().build();
|
||||
return CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(response -> {
|
||||
if (response.statusCode() == 200) {
|
||||
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
return json.get("content").getAsString();
|
||||
} else {
|
||||
LOGGER.error("Discord API returned status: {}", response.statusCode());
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
LOGGER.error("Network error while fetching Discord rules", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
|
||||
public class DiscordFormatter {
|
||||
public static MutableComponent formatRules(String rawDiscordText) {
|
||||
String processedText = convertEmojis(rawDiscordText);
|
||||
String lowerText = processedText.toLowerCase();
|
||||
ChatFormatting headerColor = ChatFormatting.DARK_PURPLE;
|
||||
ChatFormatting bodyColor = ChatFormatting.LIGHT_PURPLE;
|
||||
if (lowerText.contains("fire") || lowerText.contains("[fire]")) {
|
||||
headerColor = ChatFormatting.GOLD;
|
||||
bodyColor = ChatFormatting.YELLOW;
|
||||
} else if (lowerText.contains("frost") || lowerText.contains("[frost]")) {
|
||||
headerColor = ChatFormatting.AQUA;
|
||||
bodyColor = ChatFormatting.DARK_AQUA;
|
||||
}
|
||||
MutableComponent rootComponent = Component.empty();
|
||||
String[] lines = processedText.split("\n");
|
||||
for (String line : lines) {
|
||||
MutableComponent lineComponent;
|
||||
if (line.startsWith("**") && line.endsWith("**")) {
|
||||
String cleanLine = line.replace("**", "");
|
||||
lineComponent = Component.literal(cleanLine).withStyle(headerColor, ChatFormatting.BOLD);
|
||||
} else if (line.trim().startsWith("-") || line.trim().startsWith("\u2022")) {
|
||||
lineComponent = Component.literal(" " + line.trim()).withStyle(bodyColor);
|
||||
} else {
|
||||
lineComponent = Component.literal(line).withStyle(bodyColor);
|
||||
}
|
||||
rootComponent.append(lineComponent).append(Component.literal("\n"));
|
||||
}
|
||||
return rootComponent;
|
||||
}
|
||||
|
||||
private static String convertEmojis(String text) {
|
||||
if (text == null) return "";
|
||||
return text.replace("\uD83D\uDD25", "[Fire]").replace("\u2744\uFE0F", "[Frost]")
|
||||
.replace("\uD83D\uDC9C", "[Arcane]").replaceAll("[\\x{1F300}-\\x{1F9FF}]", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class RulesCache {
|
||||
private static String cachedRules = null;
|
||||
private static Instant lastFetchTime = Instant.MIN;
|
||||
private static final String FALLBACK_RULES =
|
||||
"[Fire] Server Rules\n1. Be respectful to all players.\n2. No griefing or cheating.\n3. Follow staff instructions.\nPlease check Discord for the full rules list.";
|
||||
|
||||
public static boolean isCacheValid() {
|
||||
if (cachedRules == null) return false;
|
||||
long cacheMinutes = ServerRulesConfig.CACHE_MINUTES.get();
|
||||
return Instant.now().isBefore(lastFetchTime.plusSeconds(cacheMinutes * 60));
|
||||
}
|
||||
|
||||
public static void updateCache(String newRules) {
|
||||
if (newRules != null && !newRules.trim().isEmpty()) {
|
||||
cachedRules = newRules;
|
||||
lastFetchTime = Instant.now();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRules() { return cachedRules != null ? cachedRules : FALLBACK_RULES; }
|
||||
public static void invalidate() { cachedRules = null; lastFetchTime = Instant.MIN; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RulesCommand {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RulesCommand.class);
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(Commands.literal("rules").executes(context -> {
|
||||
CommandSourceStack source = context.getSource();
|
||||
if (source.getEntity() == null || !(source.getEntity() instanceof ServerPlayer)) {
|
||||
source.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules()));
|
||||
return 1;
|
||||
}
|
||||
ServerPlayer player = (ServerPlayer) source.getEntity();
|
||||
if (!CooldownManager.checkAndUpdateCooldown(player)) return 0;
|
||||
if (RulesCache.isCacheValid()) {
|
||||
player.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules()));
|
||||
return 1;
|
||||
}
|
||||
player.sendSystemMessage(Component.literal("\u00A77Fetching latest rules..."));
|
||||
DiscordFetcher.fetchRulesAsync().thenAccept(fetchedRules -> {
|
||||
String rulesText;
|
||||
if (fetchedRules != null) {
|
||||
RulesCache.updateCache(fetchedRules);
|
||||
rulesText = fetchedRules;
|
||||
} else {
|
||||
LOGGER.warn("Discord fetch failed. Falling back to cached rules for {}", player.getName().getString());
|
||||
rulesText = RulesCache.getRules();
|
||||
}
|
||||
MutableComponent formattedRules = DiscordFormatter.formatRules(rulesText);
|
||||
source.getServer().execute(() -> player.sendSystemMessage(formattedRules));
|
||||
});
|
||||
return 1;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.config.ModConfigEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Mod("firefrostrules")
|
||||
public class ServerRules {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ServerRules.class);
|
||||
|
||||
public ServerRules() {
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ServerRulesConfig.SPEC);
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
LOGGER.info("Firefrost Rules Mod Initialized.");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
RulesCommand.register(event.getDispatcher());
|
||||
LOGGER.info("Registered /rules command.");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
CooldownManager.removePlayer(event.getEntity().getUUID());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onConfigReload(ModConfigEvent.Reloading event) {
|
||||
LOGGER.info("Rules configuration reloaded! Invalidating cache.");
|
||||
RulesCache.invalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class ServerRulesConfig {
|
||||
public static final ForgeConfigSpec SPEC;
|
||||
public static final ForgeConfigSpec.ConfigValue<String> BOT_TOKEN;
|
||||
public static final ForgeConfigSpec.ConfigValue<String> CHANNEL_ID;
|
||||
public static final ForgeConfigSpec.ConfigValue<String> MESSAGE_ID;
|
||||
public static final ForgeConfigSpec.IntValue COOLDOWN_SECONDS;
|
||||
public static final ForgeConfigSpec.IntValue CACHE_MINUTES;
|
||||
|
||||
static {
|
||||
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
|
||||
builder.push("discord");
|
||||
BOT_TOKEN = builder.comment("Discord Bot Token").define("bot_token", "YOUR_TOKEN_HERE");
|
||||
CHANNEL_ID = builder.comment("Discord Channel ID").define("channel_id", "1234567890123456789");
|
||||
MESSAGE_ID = builder.comment("Discord Message ID").define("message_id", "1234567890123456789");
|
||||
builder.pop();
|
||||
builder.push("performance");
|
||||
COOLDOWN_SECONDS = builder.comment("Per-player cooldown in seconds").defineInRange("cooldown_seconds", 60, 0, 3600);
|
||||
CACHE_MINUTES = builder.comment("Cache duration in minutes").defineInRange("cache_minutes", 30, 1, 1440);
|
||||
builder.pop();
|
||||
SPEC = builder.build();
|
||||
}
|
||||
|
||||
public static boolean isMessageIdValid() {
|
||||
String id = MESSAGE_ID.get();
|
||||
return StringUtils.isNotBlank(id) && id.matches("^\\d{17,20}$");
|
||||
}
|
||||
}
|
||||
28
services/rules-mod/1.20.1/src/main/resources/META-INF/mods.toml
Executable file
28
services/rules-mod/1.20.1/src/main/resources/META-INF/mods.toml
Executable file
@@ -0,0 +1,28 @@
|
||||
modLoader="javafml"
|
||||
loaderVersion="[47,)"
|
||||
license="All Rights Reserved"
|
||||
issueTrackerURL="https://firefrostgaming.com/support"
|
||||
|
||||
[[mods]]
|
||||
modId="firefrostrules"
|
||||
version="${file.jarVersion}"
|
||||
displayName="Firefrost Rules"
|
||||
displayURL="https://firefrostgaming.com"
|
||||
authors="Firefrost Gaming"
|
||||
description='''
|
||||
Fetches server rules dynamically from Discord for the /rules command.
|
||||
'''
|
||||
|
||||
[[dependencies.firefrostrules]]
|
||||
modId="forge"
|
||||
mandatory=true
|
||||
versionRange="[47,)"
|
||||
ordering="NONE"
|
||||
side="SERVER"
|
||||
|
||||
[[dependencies.firefrostrules]]
|
||||
modId="minecraft"
|
||||
mandatory=true
|
||||
versionRange="[1.20.1,1.21)"
|
||||
ordering="NONE"
|
||||
side="SERVER"
|
||||
35
services/rules-mod/1.21.1/build.gradle
Normal file
35
services/rules-mod/1.21.1/build.gradle
Normal file
@@ -0,0 +1,35 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'net.neoforged.moddev' version '2.0.141'
|
||||
}
|
||||
|
||||
version = mod_version
|
||||
group = mod_group_id
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
||||
|
||||
neoForge {
|
||||
version = neo_version
|
||||
|
||||
runs {
|
||||
server {
|
||||
server()
|
||||
}
|
||||
}
|
||||
|
||||
mods {
|
||||
"${mod_id}" {
|
||||
sourceSet sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
options.release.set(21)
|
||||
}
|
||||
10
services/rules-mod/1.21.1/gradle.properties
Normal file
10
services/rules-mod/1.21.1/gradle.properties
Normal file
@@ -0,0 +1,10 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.daemon=false
|
||||
|
||||
minecraft_version=1.21.1
|
||||
neo_version=21.1.65
|
||||
|
||||
mod_id=firefrostrules
|
||||
mod_name=Firefrost Rules
|
||||
mod_version=1.0.0
|
||||
mod_group_id=com.firefrostgaming.rules
|
||||
12
services/rules-mod/1.21.1/settings.gradle
Normal file
12
services/rules-mod/1.21.1/settings.gradle
Normal file
@@ -0,0 +1,12 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url = 'https://maven.neoforged.net/releases' }
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
|
||||
}
|
||||
|
||||
rootProject.name = 'firefrostrules'
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class CooldownManager {
|
||||
private static final ConcurrentHashMap<UUID, Instant> COOLDOWNS = new ConcurrentHashMap<>();
|
||||
|
||||
public static boolean checkAndUpdateCooldown(ServerPlayer player) {
|
||||
UUID playerId = player.getUUID();
|
||||
Instant now = Instant.now();
|
||||
int cooldownSeconds = ServerRulesConfig.COOLDOWN_SECONDS.get();
|
||||
Instant lastUsed = COOLDOWNS.get(playerId);
|
||||
if (lastUsed != null) {
|
||||
long secondsSinceLastUse = Duration.between(lastUsed, now).getSeconds();
|
||||
if (secondsSinceLastUse < cooldownSeconds) {
|
||||
long remaining = cooldownSeconds - secondsSinceLastUse;
|
||||
player.sendSystemMessage(Component.literal("\u00A7cPlease wait " + remaining + " seconds before checking the rules again."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
COOLDOWNS.put(playerId, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void removePlayer(UUID playerId) { COOLDOWNS.remove(playerId); }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordFetcher {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DiscordFetcher.class);
|
||||
private static final HttpClient CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
||||
|
||||
public static CompletableFuture<String> fetchRulesAsync() {
|
||||
if (!ServerRulesConfig.isMessageIdValid()) {
|
||||
LOGGER.error("Invalid Discord Message ID in config.");
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
String token = ServerRulesConfig.BOT_TOKEN.get();
|
||||
String channelId = ServerRulesConfig.CHANNEL_ID.get();
|
||||
String messageId = ServerRulesConfig.MESSAGE_ID.get();
|
||||
URI uri = URI.create("https://discord.com/api/v10/channels/" + channelId + "/messages/" + messageId);
|
||||
HttpRequest request = HttpRequest.newBuilder().uri(uri)
|
||||
.header("Authorization", "Bot " + token)
|
||||
.header("Accept", "application/json").GET().build();
|
||||
return CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(response -> {
|
||||
if (response.statusCode() == 200) {
|
||||
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
return json.get("content").getAsString();
|
||||
} else {
|
||||
LOGGER.error("Discord API returned status: {}", response.statusCode());
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
LOGGER.error("Network error while fetching Discord rules", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
|
||||
public class DiscordFormatter {
|
||||
public static MutableComponent formatRules(String rawDiscordText) {
|
||||
String processedText = convertEmojis(rawDiscordText);
|
||||
String lowerText = processedText.toLowerCase();
|
||||
ChatFormatting headerColor = ChatFormatting.DARK_PURPLE;
|
||||
ChatFormatting bodyColor = ChatFormatting.LIGHT_PURPLE;
|
||||
if (lowerText.contains("fire") || lowerText.contains("[fire]")) {
|
||||
headerColor = ChatFormatting.GOLD;
|
||||
bodyColor = ChatFormatting.YELLOW;
|
||||
} else if (lowerText.contains("frost") || lowerText.contains("[frost]")) {
|
||||
headerColor = ChatFormatting.AQUA;
|
||||
bodyColor = ChatFormatting.DARK_AQUA;
|
||||
}
|
||||
MutableComponent rootComponent = Component.empty();
|
||||
String[] lines = processedText.split("\n");
|
||||
for (String line : lines) {
|
||||
MutableComponent lineComponent;
|
||||
if (line.startsWith("**") && line.endsWith("**")) {
|
||||
String cleanLine = line.replace("**", "");
|
||||
lineComponent = Component.literal(cleanLine).withStyle(headerColor, ChatFormatting.BOLD);
|
||||
} else if (line.trim().startsWith("-") || line.trim().startsWith("\u2022")) {
|
||||
lineComponent = Component.literal(" " + line.trim()).withStyle(bodyColor);
|
||||
} else {
|
||||
lineComponent = Component.literal(line).withStyle(bodyColor);
|
||||
}
|
||||
rootComponent.append(lineComponent).append(Component.literal("\n"));
|
||||
}
|
||||
return rootComponent;
|
||||
}
|
||||
|
||||
private static String convertEmojis(String text) {
|
||||
if (text == null) return "";
|
||||
return text.replace("\uD83D\uDD25", "[Fire]").replace("\u2744\uFE0F", "[Frost]")
|
||||
.replace("\uD83D\uDC9C", "[Arcane]").replaceAll("[\\x{1F300}-\\x{1F9FF}]", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class RulesCache {
|
||||
private static String cachedRules = null;
|
||||
private static Instant lastFetchTime = Instant.MIN;
|
||||
private static final String FALLBACK_RULES =
|
||||
"[Fire] Server Rules\n1. Be respectful to all players.\n2. No griefing or cheating.\n3. Follow staff instructions.\nPlease check Discord for the full rules list.";
|
||||
|
||||
public static boolean isCacheValid() {
|
||||
if (cachedRules == null) return false;
|
||||
long cacheMinutes = ServerRulesConfig.CACHE_MINUTES.get();
|
||||
return Instant.now().isBefore(lastFetchTime.plusSeconds(cacheMinutes * 60));
|
||||
}
|
||||
|
||||
public static void updateCache(String newRules) {
|
||||
if (newRules != null && !newRules.trim().isEmpty()) {
|
||||
cachedRules = newRules;
|
||||
lastFetchTime = Instant.now();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRules() { return cachedRules != null ? cachedRules : FALLBACK_RULES; }
|
||||
public static void invalidate() { cachedRules = null; lastFetchTime = Instant.MIN; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RulesCommand {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RulesCommand.class);
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(Commands.literal("rules").executes(context -> {
|
||||
CommandSourceStack source = context.getSource();
|
||||
if (!source.isPlayer()) {
|
||||
source.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules()));
|
||||
return 1;
|
||||
}
|
||||
ServerPlayer player = source.getPlayer();
|
||||
if (!CooldownManager.checkAndUpdateCooldown(player)) return 0;
|
||||
if (RulesCache.isCacheValid()) {
|
||||
player.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules()));
|
||||
return 1;
|
||||
}
|
||||
player.sendSystemMessage(Component.literal("\u00A77Fetching latest rules..."));
|
||||
DiscordFetcher.fetchRulesAsync().thenAccept(fetchedRules -> {
|
||||
String rulesText;
|
||||
if (fetchedRules != null) {
|
||||
RulesCache.updateCache(fetchedRules);
|
||||
rulesText = fetchedRules;
|
||||
} else {
|
||||
LOGGER.warn("Discord fetch failed. Falling back to cached rules for {}", player.getName().getString());
|
||||
rulesText = RulesCache.getRules();
|
||||
}
|
||||
MutableComponent formattedRules = DiscordFormatter.formatRules(rulesText);
|
||||
source.getServer().execute(() -> player.sendSystemMessage(formattedRules));
|
||||
});
|
||||
return 1;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.config.ModConfig;
|
||||
import net.neoforged.fml.event.config.ModConfigEvent;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Mod("firefrostrules")
|
||||
public class ServerRules {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ServerRules.class);
|
||||
|
||||
public ServerRules(IEventBus modEventBus, ModContainer modContainer) {
|
||||
modContainer.registerConfig(ModConfig.Type.SERVER, ServerRulesConfig.SPEC);
|
||||
modEventBus.addListener(this::onConfigReload);
|
||||
NeoForge.EVENT_BUS.register(this);
|
||||
LOGGER.info("Firefrost Rules Mod Initialized.");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
RulesCommand.register(event.getDispatcher());
|
||||
LOGGER.info("Registered /rules command.");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
CooldownManager.removePlayer(event.getEntity().getUUID());
|
||||
}
|
||||
|
||||
private void onConfigReload(ModConfigEvent.Reloading event) {
|
||||
LOGGER.info("Rules configuration reloaded! Invalidating cache.");
|
||||
RulesCache.invalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.firefrostgaming.rules;
|
||||
|
||||
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class ServerRulesConfig {
|
||||
public static final ModConfigSpec SPEC;
|
||||
public static final ModConfigSpec.ConfigValue<String> BOT_TOKEN;
|
||||
public static final ModConfigSpec.ConfigValue<String> CHANNEL_ID;
|
||||
public static final ModConfigSpec.ConfigValue<String> MESSAGE_ID;
|
||||
public static final ModConfigSpec.IntValue COOLDOWN_SECONDS;
|
||||
public static final ModConfigSpec.IntValue CACHE_MINUTES;
|
||||
|
||||
static {
|
||||
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
||||
builder.push("discord");
|
||||
BOT_TOKEN = builder.comment("Discord Bot Token").define("bot_token", "YOUR_TOKEN_HERE");
|
||||
CHANNEL_ID = builder.comment("Discord Channel ID").define("channel_id", "1234567890123456789");
|
||||
MESSAGE_ID = builder.comment("Discord Message ID").define("message_id", "1234567890123456789");
|
||||
builder.pop();
|
||||
builder.push("performance");
|
||||
COOLDOWN_SECONDS = builder.comment("Per-player cooldown in seconds").defineInRange("cooldown_seconds", 60, 0, 3600);
|
||||
CACHE_MINUTES = builder.comment("Cache duration in minutes").defineInRange("cache_minutes", 30, 1, 1440);
|
||||
builder.pop();
|
||||
SPEC = builder.build();
|
||||
}
|
||||
|
||||
public static boolean isMessageIdValid() {
|
||||
String id = MESSAGE_ID.get();
|
||||
return StringUtils.isNotBlank(id) && id.matches("^\\d{17,20}$");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
modLoader="javafml"
|
||||
loaderVersion="[4,)"
|
||||
license="All Rights Reserved"
|
||||
issueTrackerURL="https://firefrostgaming.com/support"
|
||||
showAsResourcePack=false
|
||||
|
||||
[[mods]]
|
||||
modId="firefrostrules"
|
||||
version="${file.jarVersion}"
|
||||
displayName="Firefrost Rules"
|
||||
displayURL="https://firefrostgaming.com"
|
||||
authors="Firefrost Gaming"
|
||||
description='''
|
||||
Fetches server rules dynamically from Discord for the /rules command.
|
||||
Built for long-term stability and easy community management.
|
||||
'''
|
||||
|
||||
[[dependencies.firefrostrules]]
|
||||
modId="neoforge"
|
||||
type="required"
|
||||
versionRange="[21.1,)"
|
||||
ordering="NONE"
|
||||
side="SERVER"
|
||||
|
||||
[[dependencies.firefrostrules]]
|
||||
modId="minecraft"
|
||||
type="required"
|
||||
versionRange="[1.21.1,1.22)"
|
||||
ordering="NONE"
|
||||
side="SERVER"
|
||||
23
services/rules-mod/CLAUDE.md
Normal file
23
services/rules-mod/CLAUDE.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Firefrost Rules Mod — Build Environment
|
||||
|
||||
## Project Structure
|
||||
- `1.21.1/` — NeoForge (Java 21, Gradle 8.8, moddev 2.0.141)
|
||||
- `1.20.1/` — Forge (Java 17, Gradle 8.8, ForgeGradle 6.0)
|
||||
- `1.16.5/` — Forge (Java 8, Gradle 7.6.4, ForgeGradle 5.1)
|
||||
|
||||
## What This Mod Does
|
||||
Player types `/rules` → mod fetches rules from a Discord message → displays in-game with colored formatting. Admins update rules by editing a Discord message — no restarts, no file editing.
|
||||
|
||||
## 7 Source Files (each version)
|
||||
- ServerRules.java — main mod class
|
||||
- ServerRulesConfig.java — TOML config (bot token, channel ID, message ID)
|
||||
- RulesCommand.java — /rules command handler
|
||||
- DiscordFetcher.java — async HTTP fetch from Discord API
|
||||
- DiscordFormatter.java — Discord markdown → Minecraft chat formatting
|
||||
- RulesCache.java — 30-minute cache with fallback
|
||||
- CooldownManager.java — per-player 60-second cooldown
|
||||
|
||||
## Version Differences
|
||||
- 1.21.1: `net.neoforged.*`, `ModConfigSpec`, `Component.literal()`, `java.net.http.HttpClient`
|
||||
- 1.20.1: `net.minecraftforge.*`, `ForgeConfigSpec`, `Component.literal()`, `java.net.http.HttpClient`
|
||||
- 1.16.5: `net.minecraftforge.*`, `ForgeConfigSpec`, `StringTextComponent`, `HttpURLConnection`, `new JsonParser().parse()`
|
||||
Reference in New Issue
Block a user