Skip to content

Commit 26aec5d

Browse files
committed
fix: implement partial command completion and bump version to 1.0.2
1 parent 2ebde51 commit 26aec5d

File tree

4 files changed

+233
-34
lines changed

4 files changed

+233
-34
lines changed

common/src/main/java/org/netutils/mixin/ClientConnectionMixin.java

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,53 @@
2323
public class ClientConnectionMixin {
2424

2525
/**
26-
* Intercepts packets at the basic send method.
27-
* This catches all packets being sent through the connection.
26+
* Check if a packet is vital (keepalive/pong) and should not be blocked/delayed.
2827
*/
29-
@Inject(
30-
method = "send(Lnet/minecraft/network/protocol/Packet;)V",
31-
at = @At("HEAD"),
32-
cancellable = true
33-
)
34-
private void onSend(Packet<?> packet, CallbackInfo ci) {
35-
// Identify vital packets that should never be blocked/delayed (to prevent timeouts)
36-
boolean isVitalPacket = (packet instanceof ServerboundKeepAlivePacket || packet instanceof ServerboundPongPacket)
37-
&& SharedVariables.allowKeepAlive;
28+
private boolean isVitalPacket(Packet<?> packet) {
29+
return (packet instanceof ServerboundKeepAlivePacket || packet instanceof ServerboundPongPacket)
30+
&& SharedVariables.allowKeepAlive;
31+
}
32+
33+
/**
34+
* Handle packet interception logic for blocking/queueing.
35+
* @return true if the packet should be cancelled (blocked or queued)
36+
*/
37+
private boolean handlePacketInterception(Packet<?> packet) {
38+
boolean isVital = isVitalPacket(packet);
3839

3940
// Handle "Blocking" mode - Block ALL packets (except vital) if sendUIPackets is false
40-
if (!SharedVariables.sendUIPackets && !isVitalPacket) {
41-
// Log to console for debugging, but commenting out to prevent spam
42-
// System.out.println("Blocking packet: " + packet.getClass().getSimpleName());
43-
ci.cancel();
44-
return;
41+
if (!SharedVariables.sendUIPackets && !isVital) {
42+
return true; // Cancel
4543
}
4644

4745
// Handle "Delay/Queueing" mode - Queue ALL packets (except vital) if delayUIPackets is true
48-
if (SharedVariables.delayUIPackets && !isVitalPacket) {
46+
if (SharedVariables.delayUIPackets && !isVital) {
4947
SharedVariables.delayedUIPackets.add(packet);
50-
// System.out.println("Queuing packet: " + packet.getClass().getSimpleName());
51-
ci.cancel();
52-
return;
48+
return true; // Cancel
5349
}
5450

5551
// Special handling for sign editing (bypass)
56-
// Corrected logic: Use SharedVariables to track state
5752
if (!SharedVariables.shouldEditSign && packet instanceof ServerboundSignUpdatePacket) {
5853
SharedVariables.shouldEditSign = true;
59-
// System.out.println("Blocking sign update packet explicitly");
54+
return true; // Cancel
55+
}
56+
57+
return false; // Don't cancel, let it through
58+
}
59+
60+
/**
61+
* Intercepts packets at the basic send method.
62+
* This is the primary packet send method in Connection.
63+
*/
64+
@Inject(
65+
method = "send(Lnet/minecraft/network/protocol/Packet;)V",
66+
at = @At("HEAD"),
67+
cancellable = true
68+
)
69+
private void onSend(Packet<?> packet, CallbackInfo ci) {
70+
if (handlePacketInterception(packet)) {
6071
ci.cancel();
6172
}
6273
}
6374
}
75+

common/src/main/java/org/netutils/mixin/CommandSuggestionsMixin.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ public abstract class CommandSuggestionsMixin {
4343
// Check if the input starts with our command prefix
4444
if (text.startsWith(ClientCommands.PREFIX)) {
4545
// Get suggestions from our command system
46-
java.util.List<String> suggestions = ClientCommands.INSTANCE.getSuggestions(text);
46+
ClientCommands.SuggestionsResult result = ClientCommands.INSTANCE.getSuggestions(text);
47+
java.util.List<String> suggestions = result.getSuggestions();
4748

4849
if (!suggestions.isEmpty()) {
49-
// Create suggestion from our commands
50-
int start = 0;
50+
// Use the start index from our suggestion result
5151
com.mojang.brigadier.suggestion.SuggestionsBuilder builder = new com.mojang.brigadier.suggestion.SuggestionsBuilder(
52-
text, start);
52+
text, result.getStartIndex());
5353

5454
for (String suggestion : suggestions) {
5555
builder.suggest(suggestion);

common/src/main/kotlin/org/netutils/command/ClientCommands.kt

Lines changed: 194 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -342,25 +342,212 @@ object ClientCommands {
342342
sendMessage("§a[NetUtils] Chat cleared")
343343
}
344344

345+
/**
346+
* Represents the result of a suggestion request.
347+
*/
348+
data class SuggestionsResult(val suggestions: List<String>, val startIndex: Int)
349+
345350
/**
346351
* Get command suggestions for autocomplete.
352+
* Provides both command name completion and parameter-level completion.
347353
*/
348-
fun getSuggestions(input: String): List<String> {
349-
if (!input.startsWith(PREFIX)) return emptyList()
354+
fun getSuggestions(input: String): SuggestionsResult {
355+
if (!input.startsWith(PREFIX)) return SuggestionsResult(emptyList(), 0)
350356

351357
val command = input.substring(PREFIX.length)
352358

353359
// If empty, return all commands
354360
if (command.isEmpty()) {
355-
return commandNames.map { PREFIX + it }
361+
return SuggestionsResult(commandNames.map { it }, PREFIX.length)
362+
}
363+
364+
// Split into command and args
365+
// Using a regex that preserves whitespace to correctly calculate indices
366+
val parts = command.split(Regex("(?<=\\s)|(?=\\s+)"))
367+
val cmdName = parts[0].trim().lowercase()
368+
369+
// If still typing the command name (no space yet), suggest matching commands
370+
if (parts.size == 1 && !command.endsWith(" ")) {
371+
return SuggestionsResult(
372+
commandNames.filter { it.startsWith(cmdName, ignoreCase = true) },
373+
PREFIX.length
374+
)
375+
}
376+
377+
// Get the current argument being typed and its start index
378+
var currentOffset = PREFIX.length
379+
var argIndex = 0
380+
var currentArg = ""
381+
var lastArgStartIndex = PREFIX.length
382+
383+
val argParts = command.split(Regex("\\s+"))
384+
val isTrailingSpace = input.endsWith(" ")
385+
386+
if (isTrailingSpace) {
387+
argIndex = argParts.size
388+
currentArg = ""
389+
lastArgStartIndex = input.length
390+
} else {
391+
argIndex = argParts.size - 1
392+
currentArg = argParts.last()
393+
lastArgStartIndex = input.lastIndexOf(currentArg)
394+
}
395+
396+
// Get parameter suggestions for the command
397+
val cmdForParams = argParts[0].lowercase()
398+
val paramSuggestions = getParameterSuggestions(cmdForParams, argIndex, currentArg)
399+
400+
return SuggestionsResult(paramSuggestions, lastArgStartIndex)
401+
}
402+
403+
/**
404+
* Get parameter suggestions based on command and argument position.
405+
*/
406+
private fun getParameterSuggestions(cmdName: String, argIndex: Int, currentArg: String): List<String> {
407+
return when (cmdName) {
408+
// ^click <slot> <button> [type]
409+
"click" -> when (argIndex) {
410+
1 -> getSlotSuggestions(currentArg)
411+
2 -> listOf("0", "1").filter { it.startsWith(currentArg) } // 0=left, 1=right
412+
3 -> listOf("pickup", "shift", "quick_move", "swap", "clone", "throw", "quick_craft", "pickup_all")
413+
.filter { it.startsWith(currentArg, ignoreCase = true) }
414+
else -> emptyList()
415+
}
416+
417+
// ^trade <id>
418+
"trade" -> when (argIndex) {
419+
1 -> getTradeSuggestions(currentArg)
420+
else -> emptyList()
421+
}
422+
423+
// ^button <id>
424+
"button" -> when (argIndex) {
425+
1 -> (0..10).map { it.toString() }.filter { it.startsWith(currentArg) }
426+
else -> emptyList()
427+
}
428+
429+
// ^rawsend <times> <packet> [args...]
430+
"rawsend" -> when (argIndex) {
431+
1 -> listOf("1", "5", "10", "50", "100").filter { it.startsWith(currentArg) }
432+
2 -> PacketRegistry.getAllPacketKeys().filter { it.startsWith(currentArg, ignoreCase = true) }
433+
else -> getPacketArgSuggestions(getArgAt(argIndex - 2), argIndex - 2, currentArg)
434+
}
435+
436+
// ^loop <times> <command> [args...]
437+
"loop" -> when (argIndex) {
438+
1 -> listOf("1", "5", "10", "20", "50", "100").filter { it.startsWith(currentArg) }
439+
2 -> commandNames.filter { it != "loop" && it.startsWith(currentArg, ignoreCase = true) }
440+
else -> emptyList()
441+
}
442+
443+
// ^swing [hand]
444+
"swing" -> when (argIndex) {
445+
1 -> listOf("main_hand", "off_hand").filter { it.startsWith(currentArg, ignoreCase = true) }
446+
else -> emptyList()
447+
}
448+
449+
// ^drop [all]
450+
"drop" -> when (argIndex) {
451+
1 -> listOf("all").filter { it.startsWith(currentArg, ignoreCase = true) }
452+
else -> emptyList()
453+
}
454+
455+
// ^save [name] / ^load [name]
456+
"save" -> when (argIndex) {
457+
1 -> getSavedScreenSuggestions(currentArg) + listOf("default").filter { it.startsWith(currentArg, ignoreCase = true) }
458+
else -> emptyList()
459+
}
460+
"load" -> when (argIndex) {
461+
1 -> getSavedScreenSuggestions(currentArg)
462+
else -> emptyList()
463+
}
464+
465+
else -> emptyList()
356466
}
467+
}
468+
469+
/**
470+
* Get slot number suggestions based on current container.
471+
*/
472+
private fun getSlotSuggestions(currentArg: String): List<String> {
473+
val menu = mc.player?.containerMenu
474+
val slotCount = menu?.slots?.size ?: 45
357475

358-
// Return matching commands
359-
return commandNames
360-
.filter { it.startsWith(command, ignoreCase = true) }
361-
.map { PREFIX + it }
476+
// Common slots based on input prefix
477+
val suggestions = mutableListOf<String>()
478+
479+
// If empty, show common slot ranges
480+
if (currentArg.isEmpty()) {
481+
suggestions.addAll(listOf("0", "1", "2", "36", "37", "38", "39", "40", "44"))
482+
} else {
483+
// Filter slots that start with the current input
484+
for (i in 0 until slotCount) {
485+
if (i.toString().startsWith(currentArg)) {
486+
suggestions.add(i.toString())
487+
}
488+
if (suggestions.size >= 10) break
489+
}
490+
}
491+
492+
return suggestions.take(10)
362493
}
363494

495+
/**
496+
* Get trade ID suggestions based on current merchant menu.
497+
*/
498+
private fun getTradeSuggestions(currentArg: String): List<String> {
499+
val menu = mc.player?.containerMenu
500+
if (menu is MerchantMenu) {
501+
return menu.offers.indices.map { it.toString() }.filter { it.startsWith(currentArg) }
502+
}
503+
return (0..10).map { it.toString() }.filter { it.startsWith(currentArg) }
504+
}
505+
506+
/**
507+
* Get saved screen names for load/save commands.
508+
*/
509+
private fun getSavedScreenSuggestions(currentArg: String): List<String> {
510+
return try {
511+
ScreenSaver.listSlots().filter { it.startsWith(currentArg, ignoreCase = true) }
512+
} catch (e: Exception) {
513+
emptyList()
514+
}
515+
}
516+
517+
/**
518+
* Get packet-specific argument suggestions.
519+
*/
520+
private fun getPacketArgSuggestions(packetKey: String?, argIndex: Int, currentArg: String): List<String> {
521+
if (packetKey == null) return emptyList()
522+
523+
return when (packetKey) {
524+
"play.hand_swing" -> when (argIndex) {
525+
0 -> listOf("main_hand", "off_hand").filter { it.startsWith(currentArg, ignoreCase = true) }
526+
else -> emptyList()
527+
}
528+
"play.update_selected_slot" -> when (argIndex) {
529+
0 -> (0..8).map { it.toString() }.filter { it.startsWith(currentArg) }
530+
else -> emptyList()
531+
}
532+
"play.close_handled_screen" -> when (argIndex) {
533+
0 -> listOf((mc.player?.containerMenu?.containerId ?: 0).toString())
534+
else -> emptyList()
535+
}
536+
else -> emptyList()
537+
}
538+
}
539+
540+
/**
541+
* Helper to safely get argument at index from stored context.
542+
* Used for rawsend packet arg completion.
543+
*/
544+
private var lastRawsendPacket: String? = null
545+
private fun getArgAt(index: Int): String? {
546+
// This is a simplified implementation
547+
// In a full implementation, you'd track the full command context
548+
return lastRawsendPacket
549+
}
550+
364551
private fun sendMessage(message: String) {
365552
mc.player?.displayClientMessage(Component.literal(message), false)
366553
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Mod Properties
2-
mod_version=1.0.1
2+
mod_version=1.0.2
33
maven_group=org.netutils
44
archives_base_name=netutils
55

0 commit comments

Comments
 (0)