package fun import ( "fmt" "math/rand" "regexp" "strconv" "strings" "time" "git.nakama.town/fmartingr/butterrobot/internal/model" "git.nakama.town/fmartingr/butterrobot/internal/plugin" ) // DicePlugin rolls dice based on standard dice notation type DicePlugin struct { plugin.BasePlugin rand *rand.Rand } // NewDice creates a new DicePlugin instance func NewDice() *DicePlugin { source := rand.NewSource(time.Now().UnixNano()) return &DicePlugin{ BasePlugin: plugin.BasePlugin{ ID: "fun.dice", Name: "Dice Roller", Help: "Rolls dice when you type '!dice [formula]' (default: 1d20)", }, rand: rand.New(source), } } // OnMessage handles incoming messages func (p *DicePlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message { if !strings.HasPrefix(strings.TrimSpace(strings.ToLower(msg.Text)), "!dice") { return nil } // Extract dice formula formula := strings.TrimSpace(strings.TrimPrefix(msg.Text, "!dice")) formula = strings.TrimSpace(strings.TrimPrefix(formula, "!dice")) if formula == "" { formula = "1d20" // Default formula } // Parse and roll the dice result, err := p.rollDice(formula) responseText := "" if err != nil { responseText = fmt.Sprintf("Error: %s", err.Error()) } else { responseText = fmt.Sprintf("%d", result) } response := &model.Message{ Text: responseText, Chat: msg.Chat, ReplyTo: msg.ID, Channel: msg.Channel, } return []*model.Message{response} } // rollDice parses a dice formula string and returns the result func (p *DicePlugin) rollDice(formula string) (int, error) { // Support basic dice notation like "2d6", "1d20+5", etc. diceRegex := regexp.MustCompile(`^(\d+)d(\d+)(?:([+-])(\d+))?$`) matches := diceRegex.FindStringSubmatch(formula) if matches == nil { return 0, fmt.Errorf("invalid dice formula: %s", formula) } // Parse number of dice numDice, err := strconv.Atoi(matches[1]) if err != nil || numDice < 1 { return 0, fmt.Errorf("invalid number of dice") } if numDice > 100 { return 0, fmt.Errorf("too many dice (max 100)") } // Parse number of sides sides, err := strconv.Atoi(matches[2]) if err != nil || sides < 1 { return 0, fmt.Errorf("invalid number of sides") } if sides > 1000 { return 0, fmt.Errorf("too many sides (max 1000)") } // Roll the dice total := 0 for i := 0; i < numDice; i++ { roll := p.rand.Intn(sides) + 1 total += roll } // Apply modifier if present if len(matches) > 3 && matches[3] != "" { modifier, err := strconv.Atoi(matches[4]) if err != nil { return 0, fmt.Errorf("invalid modifier") } if matches[3] == "+" { total += modifier } else if matches[3] == "-" { total -= modifier } } return total, nil }