Repository: sealdice/sealdice-core (language: go)

Issue:
[Bug] 规则模板插件替换升级后，已在群中加载的模板不会自动替换
如图所示。在截图内容发生前，已经将插件替换了版本并重载。第一次调用 `rw` 指令仍然采用了已加载的旧模板。在重新加载模板后，第二次指令调用使用了新模板。

![c19225a9c744c96d7d1fb21e792ca719](https://github.com/user-attachments/assets/3cf883c6-a07c-42a7-a275-d08583ff4945)




Discussion / hints:

Fix patch:
diff --git a/dice/config.go b/dice/config.go
index 3938b0cd..f1a14fbe 100644
--- a/dice/config.go
+++ b/dice/config.go
@@ -14,6 +14,7 @@ import (
 	"time"
 
 	wr "github.com/mroth/weightedrand"
+	"github.com/samber/lo"
 	"gopkg.in/yaml.v3"
 
 	"sealdice-core/dice/model"
@@ -2133,6 +2134,9 @@ func (d *Dice) loads() {
 		d.ImSession.ServiceAtNew.Range(func(_ string, groupInfo *GroupInfo) bool {
 			// Pinenutn: ServiceAtNew重构
 			var tmp []*ExtInfo
+			groupInfo.ExtListSnapshot = lo.Map(groupInfo.ActivatedExtList, func(item *ExtInfo, index int) string {
+				return item.Name
+			})
 			for _, i := range groupInfo.ActivatedExtList {
 				if m[i.Name] != nil {
 					tmp = append(tmp, m[i.Name])
diff --git a/dice/dice.go b/dice/dice.go
index edbdd3a3..9fc4ca89 100644
--- a/dice/dice.go
+++ b/dice/dice.go
@@ -623,9 +623,10 @@ func (d *Dice) ApplyAliveNotice() {
 	}
 }
 
-// GameSystemTemplateAdd 应用一个角色模板
-func (d *Dice) GameSystemTemplateAdd(tmpl *GameSystemTemplate) bool {
-	if _, exists := d.GameSystemMap.Load(tmpl.Name); !exists {
+// GameSystemTemplateAddEx 应用一个角色模板
+func (d *Dice) GameSystemTemplateAddEx(tmpl *GameSystemTemplate, overwrite bool) bool {
+	_, exists := d.GameSystemMap.Load(tmpl.Name)
+	if !exists || overwrite {
 		d.GameSystemMap.Store(tmpl.Name, tmpl)
 		// sn 从这里读取
 		// set 时从这里读取对应System名字的模板
@@ -644,6 +645,11 @@ func (d *Dice) GameSystemTemplateAdd(tmpl *GameSystemTemplate) bool {
 	return false
 }
 
+// GameSystemTemplateAdd 应用一个角色模板，当已存在时返回false
+func (d *Dice) GameSystemTemplateAdd(tmpl *GameSystemTemplate) bool {
+	return d.GameSystemTemplateAddEx(tmpl, false)
+}
+
 // var randSource = rand2.NewSource(uint64(time.Now().Unix()))
 var randSource = &rand2.PCGSource{}
 
diff --git a/dice/dice_jsvm.go b/dice/dice_jsvm.go
index 90ad02f3..fe325566 100644
--- a/dice/dice_jsvm.go
+++ b/dice/dice_jsvm.go
@@ -203,7 +203,7 @@ func (d *Dice) JsInit() {
 			// Pinenutn: Range模板 ServiceAtNew重构代码
 			d.ImSession.ServiceAtNew.Range(func(key string, groupInfo *GroupInfo) bool {
 				// Pinenutn: ServiceAtNew重构
-				groupInfo.ExtActive(ei)
+				groupInfo.ExtActiveBySnapshotOrder(ei, true)
 				return true
 			})
 		})
@@ -511,7 +511,7 @@ func (d *Dice) JsInit() {
 			if err != nil {
 				return errors.New("解析失败:" + err.Error())
 			}
-			ret := d.GameSystemTemplateAdd(tmpl)
+			ret := d.GameSystemTemplateAddEx(tmpl, true)
 			if !ret {
 				return errors.New("已存在同名模板")
 			}
@@ -523,7 +523,7 @@ func (d *Dice) JsInit() {
 			if err != nil {
 				return errors.New("解析失败:" + err.Error())
 			}
-			ret := d.GameSystemTemplateAdd(tmpl)
+			ret := d.GameSystemTemplateAddEx(tmpl, true)
 			if !ret {
 				return errors.New("已存在同名模板")
 			}
@@ -784,6 +784,15 @@ func (d *Dice) JsReload() {
 		d.JsScriptCron.Stop()
 		d.JsScriptCron = nil
 	}
+
+	// 记录扩展快照
+	d.ImSession.ServiceAtNew.Range(func(key string, groupInfo *GroupInfo) bool {
+		groupInfo.ExtListSnapshot = lo.Map(groupInfo.ActivatedExtList, func(item *ExtInfo, index int) string {
+			return item.Name
+		})
+		return true
+	})
+
 	d.JsInit()
 	_ = d.ConfigManager.Load()
 	d.JsLoadScripts()
diff --git a/dice/im_session.go b/dice/im_session.go
index 87fd462b..d3644191 100644
--- a/dice/im_session.go
+++ b/dice/im_session.go
@@ -11,6 +11,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/samber/lo"
 	"gorm.io/gorm"
 
 	"sealdice-core/dice/model"
@@ -80,6 +81,7 @@ type GroupPlayerInfo model.GroupPlayerInfoBase
 type GroupInfo struct {
 	Active           bool                               `json:"active" yaml:"active" jsbind:"active"`          // 是否在群内开启 - 过渡为象征意义
 	ActivatedExtList []*ExtInfo                         `yaml:"activatedExtList,flow" json:"activatedExtList"` // 当前群开启的扩展列表
+	ExtListSnapshot  []string                           `yaml:"-" json:"-"`                                    // 存放当前激活的扩展表，无论其是否存在，用于处理插件重载后优先级混乱的问题
 	Players          *SyncMap[string, *GroupPlayerInfo] `yaml:"-" json:"-"`                                    // 群员角色数据
 
 	GroupID         string                 `yaml:"groupId" json:"groupId" jsbind:"groupId"`
@@ -128,6 +130,35 @@ func (group *GroupInfo) ExtActive(ei *ExtInfo) {
 	group.ExtClear()
 }
 
+// ExtActiveBySnapshotOrder 按照快照顺序开启扩展
+func (group *GroupInfo) ExtActiveBySnapshotOrder(ei *ExtInfo, isFirstTimeLoad bool) {
+	// 这个机制用于解决js插件指令会覆盖原生扩展的指令的问题
+	// 与之相关的问题是插件的自动激活，最好能够检测插件是否为首次加载
+	orderLst := group.ExtListSnapshot
+	m := map[string]*ExtInfo{}
+	for _, i := range group.ActivatedExtList {
+		m[i.Name] = i
+	}
+	m[ei.Name] = ei
+
+	var newLst []*ExtInfo
+	for _, i := range orderLst {
+		if m[i] != nil {
+			newLst = append(newLst, m[i])
+		}
+	}
+
+	// 当首次加载，如果快照列表中没有，将其新增
+	if isFirstTimeLoad {
+		if !lo.Contains(orderLst, ei.Name) {
+			newLst = append(newLst, ei)
+		}
+	}
+
+	group.ActivatedExtList = newLst
+	group.ExtClear()
+}
+
 // ExtClear 清除多余的扩展项
 func (group *GroupInfo) ExtClear() {
 	m := map[string]bool{}


Test patch: