package commands import ( "context" "fmt" "strings" ) // NewCommandManager creates a new command manager instance func NewCommandManager() *CommandManager { return &CommandManager{ commands: make(map[string]*Command), } } // Register registers a command with the manager func (cm *CommandManager) Register(command *Command) error { if command == nil { return fmt.Errorf("command cannot be nil") } if command.Name == "" { return fmt.Errorf("command name cannot be empty") } if command.Handler == nil { return fmt.Errorf("command handler cannot be nil") } cm.mutex.Lock() defer cm.mutex.Unlock() // Normalize command name to lowercase for case-insensitive matching normalizedName := strings.ToLower(command.Name) if _, exists := cm.commands[normalizedName]; exists { return fmt.Errorf("command '%s' is already registered", command.Name) } cm.commands[normalizedName] = command return nil } // RegisterSubCommand registers a subcommand under a parent command func (cm *CommandManager) RegisterSubCommand(parentName string, subCommand *Command) error { if subCommand == nil { return fmt.Errorf("subcommand cannot be nil") } cm.mutex.Lock() defer cm.mutex.Unlock() normalizedParent := strings.ToLower(parentName) parentCmd, exists := cm.commands[normalizedParent] if !exists { return fmt.Errorf("parent command '%s' not found", parentName) } if parentCmd.SubCommands == nil { parentCmd.SubCommands = make(map[string]*Command) } normalizedSubName := strings.ToLower(subCommand.Name) if _, exists := parentCmd.SubCommands[normalizedSubName]; exists { return fmt.Errorf("subcommand '%s' already exists under '%s'", subCommand.Name, parentName) } parentCmd.SubCommands[normalizedSubName] = subCommand return nil } // Unregister removes a command from the manager func (cm *CommandManager) Unregister(name string) { cm.mutex.Lock() defer cm.mutex.Unlock() normalizedName := strings.ToLower(name) delete(cm.commands, normalizedName) } // HasCommand checks if a command is registered func (cm *CommandManager) HasCommand(name string) bool { cm.mutex.RLock() defer cm.mutex.RUnlock() normalizedName := strings.ToLower(name) _, exists := cm.commands[normalizedName] return exists } // GetCommand retrieves a command by name func (cm *CommandManager) GetCommand(name string) (*Command, bool) { cm.mutex.RLock() defer cm.mutex.RUnlock() normalizedName := strings.ToLower(name) command, exists := cm.commands[normalizedName] return command, exists } // ListCommands returns all registered command names func (cm *CommandManager) ListCommands() []string { cm.mutex.RLock() defer cm.mutex.RUnlock() names := make([]string, 0, len(cm.commands)) for name := range cm.commands { names = append(names, name) } return names } // ListCommandsByType returns commands filtered by type func (cm *CommandManager) ListCommandsByType(commandType CommandType) []*Command { cm.mutex.RLock() defer cm.mutex.RUnlock() commands := make([]*Command, 0) for _, cmd := range cm.commands { if cmd.Type == commandType { commands = append(commands, cmd) } } return commands } // Execute executes a command with the given context func (cm *CommandManager) Execute(ctx *CommandContext) error { if ctx == nil { return fmt.Errorf("command context cannot be nil") } if ctx.CommandName == "" { return fmt.Errorf("command name cannot be empty") } // Find the command normalizedName := strings.ToLower(ctx.CommandName) cm.mutex.RLock() command, exists := cm.commands[normalizedName] cm.mutex.RUnlock() if !exists { return fmt.Errorf("unknown command: %s", ctx.CommandName) } // Check admin level requirements if ctx.AdminLevel < command.RequiredLevel { return fmt.Errorf("insufficient privileges for command '%s' (required: %d, have: %d)", command.Name, command.RequiredLevel, ctx.AdminLevel) } // Check for subcommands if len(ctx.Arguments) > 0 && command.SubCommands != nil { normalizedSubName := strings.ToLower(ctx.Arguments[0]) if subCommand, exists := command.SubCommands[normalizedSubName]; exists { // Execute subcommand subCtx := &CommandContext{ Context: ctx.Context, CommandType: subCommand.Type, CommandName: subCommand.Name, Arguments: ctx.Arguments[1:], // Remove subcommand name from arguments RawArguments: strings.TrimSpace(strings.Join(ctx.Arguments[1:], " ")), AdminLevel: ctx.AdminLevel, RequiredLevel: subCommand.RequiredLevel, Client: ctx.Client, Player: ctx.Player, Target: ctx.Target, Zone: ctx.Zone, Messages: make([]CommandMessage, 0), Results: make(map[string]any), } // Check subcommand admin level if ctx.AdminLevel < subCommand.RequiredLevel { return fmt.Errorf("insufficient privileges for subcommand '%s %s' (required: %d, have: %d)", command.Name, subCommand.Name, subCommand.RequiredLevel, ctx.AdminLevel) } return subCommand.Handler(subCtx) } } // Execute main command return command.Handler(ctx) } // ParseCommand parses a raw command string into a CommandContext func (cm *CommandManager) ParseCommand(rawCommand string, client ClientInterface) *CommandContext { // Remove leading slash if present rawCommand = strings.TrimSpace(rawCommand) rawCommand = strings.TrimPrefix(rawCommand, "/") // Split into command and arguments parts := strings.Fields(rawCommand) if len(parts) == 0 { return nil } commandName := parts[0] arguments := parts[1:] rawArguments := strings.TrimSpace(strings.Join(arguments, " ")) // Determine admin level adminLevel := AdminLevelPlayer if client != nil { adminLevel = client.GetAdminLevel() } // Create context ctx := &CommandContext{ Context: context.Background(), CommandName: commandName, Arguments: arguments, RawArguments: rawArguments, AdminLevel: adminLevel, Client: client, Messages: make([]CommandMessage, 0), Results: make(map[string]any), } // Set additional context fields if client is available if client != nil { ctx.Player = client.GetPlayer() ctx.Zone = client.GetZone() } // Determine command type based on name ctx.CommandType = cm.determineCommandType(commandName) return ctx } // determineCommandType determines the command type based on the command name func (cm *CommandManager) determineCommandType(commandName string) CommandType { // Check if command exists and get its type cm.mutex.RLock() defer cm.mutex.RUnlock() normalizedName := strings.ToLower(commandName) if command, exists := cm.commands[normalizedName]; exists { return command.Type } // Default to player command if not found return CommandTypePlayer }