118 lines
		
	
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			118 lines
		
	
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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
 | |
| }
 |