@@ -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 }
0 commit comments