Skip to content

Commit fe2d2a5

Browse files
committed
feat: Add collect XP button to GUI and enhance Discord logging
- Introduced a new "Collect XP" button in the GUI with customizable placeholders. - Updated language files for English, German, Vietnamese, and DonutSMP to include the new button. - Removed obsolete CHANGELOG.yml file and consolidated changelog information into CHANGELOG.txt. - Enhanced Discord webhook integration by moving embed configurations to per-event files. - Added detailed documentation for the new `/ss near` command, including features and parameters. - Updated configuration documentation to include spawner action logging settings. - Improved performance notes for Discord webhook integration. - Updated permissions documentation to include new command permissions for scanning nearby spawners.
1 parent c188687 commit fe2d2a5

37 files changed

Lines changed: 1217 additions & 1905 deletions

CHANGELOG.md

Lines changed: 0 additions & 26 deletions
This file was deleted.

core/build.gradle.kts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import java.net.URI
2+
import java.time.LocalDate
3+
14
plugins {
25
id("com.gradleup.shadow")
36
}
@@ -119,3 +122,112 @@ tasks.processResources {
119122
}
120123
}
121124

125+
// ── Language Changelog generation ────────────────────────────────────────────
126+
//
127+
// Run this task manually before releasing a new version:
128+
//
129+
// ./gradlew generateLanguageChangelog
130+
//
131+
// It compares the current project version against the latest published GitHub
132+
// release. If the build is newer it prepends a new skeleton entry to
133+
// core/src/main/resources/language/CHANGELOG.txt – the human-readable file
134+
// that is extracted to plugins/SmartSpawner/language/CHANGELOG.txt on every
135+
// server start so admins know which language keys changed.
136+
//
137+
// The task is skipped gracefully when GitHub is unreachable (offline / CI
138+
// without network access).
139+
// ─────────────────────────────────────────────────────────────────────────────
140+
tasks.register("generateLanguageChangelog") {
141+
group = "documentation"
142+
description = "Prepends a new skeleton entry to language/CHANGELOG.txt when the build version exceeds the latest GitHub release."
143+
144+
val changelogFile = project.file("src/main/resources/language/CHANGELOG.txt")
145+
146+
inputs.property("projectVersion", project.version.toString())
147+
outputs.file(changelogFile)
148+
149+
doLast {
150+
val currentVersion = project.version.toString()
151+
152+
// ── 1. Fetch latest GitHub release tag ───────────────────────────────
153+
val githubVersion: String = try {
154+
val conn = URI.create(
155+
"https://api.github.com/repos/NighterDevelopment/SmartSpawner/releases/latest"
156+
).toURL().openConnection() as java.net.HttpURLConnection
157+
conn.requestMethod = "GET"
158+
conn.setRequestProperty("Accept", "application/vnd.github.v3+json")
159+
conn.setRequestProperty("User-Agent", "SmartSpawner-Changelog-Bot/1.0")
160+
conn.connectTimeout = 6_000
161+
conn.readTimeout = 6_000
162+
if (conn.responseCode == 200) {
163+
val body = conn.inputStream.bufferedReader().readText()
164+
Regex(""""tag_name"\s*:\s*"v?([^"]+)"""").find(body)
165+
?.groupValues?.get(1) ?: "0.0.0"
166+
} else {
167+
println("[changelog] GitHub API returned HTTP ${conn.responseCode} – skipping.")
168+
return@doLast
169+
}
170+
} catch (e: Exception) {
171+
println("[changelog] ⚠ Cannot reach GitHub API (${e.message}) – skipping changelog update.")
172+
return@doLast
173+
}
174+
175+
// ── 2. Compare versions ──────────────────────────────────────────────
176+
fun parseVer(v: String): List<Int> =
177+
v.removePrefix("v").split(".").map { it.toIntOrNull() ?: 0 }
178+
179+
val cur = parseVer(currentVersion)
180+
val gh = parseVer(githubVersion)
181+
var isNewer = false
182+
for (i in 0 until maxOf(cur.size, gh.size)) {
183+
val a = cur.getOrElse(i) { 0 }
184+
val b = gh.getOrElse(i) { 0 }
185+
if (a > b) { isNewer = true; break }
186+
if (a < b) break
187+
}
188+
189+
if (!isNewer) {
190+
println("[changelog] Up-to-date (build=$currentVersion, github=$githubVersion) – nothing to add.")
191+
return@doLast
192+
}
193+
194+
// ── 3. Guard against duplicates ──────────────────────────────────────
195+
val existing = if (changelogFile.exists()) changelogFile.readText() else ""
196+
if (existing.contains("── v$currentVersion")) {
197+
println("[changelog] Version $currentVersion already present – skipping.")
198+
return@doLast
199+
}
200+
201+
// ── 4. Build plain-text entry (matches CHANGELOG.txt style) ──────────
202+
val today = LocalDate.now().toString()
203+
val separator = "".repeat(80 - "── v$currentVersion ($today) ".length)
204+
val newEntry = buildString {
205+
appendLine("── v$currentVersion ($today) $separator")
206+
appendLine()
207+
appendLine(" Summary: Version $currentVersion released – fill in details here.")
208+
appendLine(" Compare: https://github.com/NighterDevelopment/SmartSpawner/compare/v$githubVersion...v$currentVersion")
209+
appendLine()
210+
appendLine(" ADDED:")
211+
appendLine(" (none)")
212+
appendLine()
213+
appendLine(" CHANGED:")
214+
appendLine(" (none)")
215+
appendLine()
216+
appendLine(" REMOVED:")
217+
appendLine(" (none)")
218+
appendLine()
219+
}
220+
221+
// ── 5. Insert before the first existing version entry ────────────────
222+
val marker = "\n──"
223+
val updated = if (existing.contains(marker)) {
224+
existing.replaceFirst(marker, "\n$newEntry──")
225+
} else {
226+
"$existing\n$newEntry"
227+
}
228+
229+
changelogFile.writeText(updated)
230+
println("[changelog] ✓ Prepended entry for v$currentVersion into language/CHANGELOG.txt")
231+
}
232+
}
233+

core/src/main/java/github/nighter/smartspawner/SmartSpawner.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import github.nighter.smartspawner.bstats.Metrics;
55
import github.nighter.smartspawner.commands.BrigadierCommandManager;
66
import github.nighter.smartspawner.commands.list.ListSubCommand;
7+
import github.nighter.smartspawner.commands.near.SpawnerHighlightManager;
78
import github.nighter.smartspawner.commands.list.gui.list.UserPreferenceCache;
89
import github.nighter.smartspawner.commands.list.gui.list.SpawnerListGUI;
910
import github.nighter.smartspawner.commands.list.gui.management.SpawnerManagementHandler;
@@ -61,6 +62,7 @@
6162
import github.nighter.smartspawner.updates.ConfigUpdater;
6263
import github.nighter.smartspawner.nms.VersionInitializer;
6364
import github.nighter.smartspawner.updates.LanguageUpdater;
65+
import github.nighter.smartspawner.updates.LanguageChangelogUpdater;
6466
import github.nighter.smartspawner.updates.UpdateChecker;
6567
import github.nighter.smartspawner.spawner.utils.SpawnerTypeChecker;
6668
import github.nighter.smartspawner.spawner.utils.SpawnerLocationLockManager;
@@ -152,6 +154,9 @@ public class SmartSpawner extends JavaPlugin implements SmartSpawnerPlugin {
152154
private SpawnerAuditListener spawnerAuditListener;
153155
private LoggingConfig loggingConfig;
154156

157+
// Near-command highlight manager
158+
private SpawnerHighlightManager spawnerHighlightManager;
159+
155160
// API implementation
156161
private SmartSpawnerAPIImpl apiImpl;
157162

@@ -243,6 +248,7 @@ private void initializeServices() {
243248
configUpdater.checkAndUpdateConfig();
244249
this.languageManager = new LanguageManager(this);
245250
this.languageUpdater = new LanguageUpdater(this);
251+
new LanguageChangelogUpdater(this).update();
246252
this.messageService = new MessageService(this, languageManager);
247253

248254
// Initialize new unified spawner settings config (but don't load yet)
@@ -402,6 +408,7 @@ private void initializeHandlers() {
402408
this.spawnEggHandler = new SpawnEggHandler(this);
403409
this.spawnerStackHandler = new SpawnerStackHandler(this);
404410
this.spawnerClickManager = new SpawnerClickManager(this);
411+
this.spawnerHighlightManager = new SpawnerHighlightManager(this);
405412
}
406413

407414
private void initializeUIAndActions() {
@@ -453,6 +460,11 @@ private void registerListeners() {
453460
pm.registerEvents(pricesGUI, this);
454461
pm.registerEvents(spawnerSellConfirmListener, this);
455462

463+
// Register near-command listener (player quit cleanup)
464+
if (spawnerHighlightManager != null) {
465+
pm.registerEvents(spawnerHighlightManager, this);
466+
}
467+
456468
// Register logging listener
457469
if (spawnerAuditListener != null) {
458470
pm.registerEvents(spawnerAuditListener, this);
@@ -569,6 +581,11 @@ private void saveAndCleanup() {
569581
spawnerActionLogger.shutdown();
570582
}
571583

584+
// Clean up spawner highlight sessions
585+
if (spawnerHighlightManager != null) {
586+
spawnerHighlightManager.cleanup();
587+
}
588+
572589
// Clean up resources
573590
cleanupResources();
574591
}

core/src/main/java/github/nighter/smartspawner/commands/MainCommand.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import github.nighter.smartspawner.commands.give.GiveSubCommand;
88
import github.nighter.smartspawner.commands.hologram.HologramSubCommand;
99
import github.nighter.smartspawner.commands.list.ListSubCommand;
10+
import github.nighter.smartspawner.commands.near.NearSubCommand;
1011
import github.nighter.smartspawner.commands.prices.PricesSubCommand;
1112
import github.nighter.smartspawner.commands.reload.ReloadSubCommand;
1213
import github.nighter.smartspawner.language.MessageService;
@@ -37,7 +38,8 @@ public MainCommand(SmartSpawner plugin) {
3738
new ListSubCommand(plugin),
3839
new HologramSubCommand(plugin),
3940
new PricesSubCommand(plugin),
40-
new ClearSubCommand(plugin)
41+
new ClearSubCommand(plugin),
42+
new NearSubCommand(plugin, plugin.getSpawnerHighlightManager())
4143
);
4244
}
4345

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package github.nighter.smartspawner.commands.near;
2+
3+
import com.mojang.brigadier.arguments.IntegerArgumentType;
4+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
5+
import com.mojang.brigadier.context.CommandContext;
6+
import github.nighter.smartspawner.SmartSpawner;
7+
import github.nighter.smartspawner.commands.BaseSubCommand;
8+
import io.papermc.paper.command.brigadier.CommandSourceStack;
9+
import io.papermc.paper.command.brigadier.Commands;
10+
import org.bukkit.command.CommandSender;
11+
import org.bukkit.entity.Player;
12+
import org.jspecify.annotations.NullMarked;
13+
14+
@NullMarked
15+
public class NearSubCommand extends BaseSubCommand {
16+
17+
private static final int DEFAULT_RADIUS = 50;
18+
19+
private final SpawnerHighlightManager highlightManager;
20+
21+
public NearSubCommand(SmartSpawner plugin, SpawnerHighlightManager highlightManager) {
22+
super(plugin);
23+
this.highlightManager = highlightManager;
24+
}
25+
26+
@Override
27+
public String getName() {
28+
return "near";
29+
}
30+
31+
@Override
32+
public String getPermission() {
33+
return "smartspawner.command.near";
34+
}
35+
36+
@Override
37+
public String getDescription() {
38+
return "Highlight nearby spawners through walls";
39+
}
40+
41+
@Override
42+
public LiteralArgumentBuilder<CommandSourceStack> build() {
43+
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal(getName());
44+
builder.requires(source -> hasPermission(source.getSender()));
45+
46+
// /ss near – scan with default radius
47+
builder.executes(this::execute);
48+
49+
// /ss near <radius> – scan with custom radius (1..MAX_RADIUS)
50+
builder.then(
51+
Commands.argument("radius", IntegerArgumentType.integer(1, SpawnerHighlightManager.MAX_RADIUS))
52+
.executes(context ->
53+
executeScan(context, IntegerArgumentType.getInteger(context, "radius")))
54+
);
55+
56+
// /ss near cancel – cancel active scan + remove highlights
57+
builder.then(
58+
Commands.literal("cancel")
59+
.executes(this::executeCancel)
60+
);
61+
62+
return builder;
63+
}
64+
65+
@Override
66+
public int execute(CommandContext<CommandSourceStack> context) {
67+
return executeScan(context, DEFAULT_RADIUS);
68+
}
69+
70+
private int executeScan(CommandContext<CommandSourceStack> context, int radius) {
71+
CommandSender sender = context.getSource().getSender();
72+
logCommandExecution(context);
73+
74+
if (!(sender instanceof Player player)) {
75+
plugin.getMessageService().sendMessage(sender, "command_player_only");
76+
return 0;
77+
}
78+
79+
highlightManager.startScan(player, radius);
80+
return 1;
81+
}
82+
83+
private int executeCancel(CommandContext<CommandSourceStack> context) {
84+
CommandSender sender = context.getSource().getSender();
85+
logCommandExecution(context);
86+
87+
if (!(sender instanceof Player player)) {
88+
plugin.getMessageService().sendMessage(sender, "command_player_only");
89+
return 0;
90+
}
91+
92+
highlightManager.cancelScan(player);
93+
return 1;
94+
}
95+
}

0 commit comments

Comments
 (0)