248 lines
6.6 KiB
Go

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
}