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

Issue:
[Bug]: 替换 DS 后掷默认骰详情输出不一致
### 在提问之前...

- [X] 我理解 Issue 是用于反馈和解决问题的，而非吐槽评论区，将尽可能提供更多信息帮助问题解决
- [X] 我填写了简短且清晰明确的标题，以便开发者在翻阅 issue 列表时能快速确定大致问题。而不是“一个建议”、“卡住了”等
- [X] 我已查看master branch或最新测试版的更新内容，并未提及该 bug 已被修复的情况
- [X] 已有issue中并没有看见其他人与我反馈相同的问题

### 问题描述

![180efe3d9c0951e3684c1f9e5e18e77f](https://github.com/sealdice/sealdice-core/assets/45380560/361e6a08-9e01-4159-979d-9f7565959595)


### 如何复现

不明

### 你期望发生的

应与截图中 Kal'tsit 的格式类似，即 `d=[1d100=86]=86`，或 `d=[1d20=3]=3`

### 实际上发生的

/

### 日志文件

/

### 截图

_No response_

### 海豹核心版本

1.5.0-dev+20240702.82c386b

### 操作系统

/

### 帐号类型

/

### 使用协议

_No response_

### 附加内容

_No response_


Discussion / hints:
似与群组默认骰面有关

![1ea3f73c113a99ed98915610c0012b4f_720](https://github.com/sealdice/sealdice-core/assets/45380560/f41c5d8e-f080-45c2-9c46-1872a75c2c2f)

根本原因是因为没有设置群组面数用ds解析会失败，然后fall到v1
所以输出是rvm v1的格式
如果ds不支持这种写法，我的建议是趁早回滚

初步认为是因为ds使用表达式(string)替代了数值的骰子面数作为d的值，旧的配置需要转换后输入进去，但是可能没有配置正确
TODO: 表达式d应该显示计算过程，而不是采取与d100相同的显示
> 初步认为是因为ds使用表达式(string)替代了数值的骰子面数作为d的值，旧的配置需要转换后输入进去，但是可能没有配置正确

如果你说的是规则模板里的 DiceExpr，实际检查代码发现这个字段没有被使用

Fix patch:
diff --git a/dice/ext_dnd5e.go b/dice/ext_dnd5e.go
index 752c49ed..1aa7118d 100644
--- a/dice/ext_dnd5e.go
+++ b/dice/ext_dnd5e.go
@@ -214,6 +214,7 @@ func RegisterBuiltinExtDnd5e(self *Dice) {
 			ctx.setDndReadForVM(false)
 			return nil
 		},
+		ToExport: toExport,
 		ToMod: func(ctx *MsgContext, args *CmdArgs, i *stSetOrModInfoItem, attrs *AttributesItem, tmpl *GameSystemTemplate) bool {
 			over := args.GetKwarg("over")
 			attrName := tmpl.GetAlias(i.name)
@@ -277,8 +278,6 @@ func RegisterBuiltinExtDnd5e(self *Dice) {
 					Expr:  fmt.Sprintf("pbCalc(this.base, this.factor, %s)", parent),
 					Attrs: &m,
 				})
-				attrs.Store(attrName, i.value)
-				return true
 			} else if isAbilityScores(attrName) {
 				// 如果为主要属性，同时读取豁免值
 				if i.extra != nil {
@@ -286,8 +285,6 @@ func RegisterBuiltinExtDnd5e(self *Dice) {
 				} else {
 					attrs.Delete(stpFormat(attrName))
 				}
-				attrs.Store(attrName, i.value)
-				return true
 			}
 			return false
 		},
@@ -332,7 +329,6 @@ func RegisterBuiltinExtDnd5e(self *Dice) {
 
 				r := ctx.Eval(expr, nil)
 				if r.vm.Error != nil {
-					fmt.Println("xxx", restText)
 					ReplyToSender(mctx, msg, "无法解析表达式: "+restText)
 					return CmdExecuteResult{Matched: true, Solved: true}
 				}
@@ -488,7 +484,6 @@ func RegisterBuiltinExtDnd5e(self *Dice) {
 			ctx.setDndReadForVM(false)
 			return nil
 		},
-		ToExport: toExport,
 		ToSet: func(ctx *MsgContext, i *stSetOrModInfoItem, attrs *AttributesItem, tmpl *GameSystemTemplate) bool {
 			attrName := tmpl.GetAlias(i.name)
 			i.name = "$buff_" + attrName
@@ -1063,7 +1058,7 @@ func RegisterBuiltinExtDnd5e(self *Dice) {
 					name = mctx.Player.Name
 					// 情况2，名字是自己，没有加值
 					if !exprExists {
-						val = int64(ds.Roll(nil, 20))
+						val = int64(ds.Roll(nil, 20, 0))
 					}
 					uid = mctx.Player.UserID
 					return 0, name, val, detail, uid
@@ -1076,7 +1071,7 @@ func RegisterBuiltinExtDnd5e(self *Dice) {
 					name = m[1]
 					text = text[len(m[0]):]
 					if !exprExists {
-						val = int64(ds.Roll(nil, 20))
+						val = int64(ds.Roll(nil, 20, 0))
 					}
 				} else {
 					// 不知道是啥，报错
diff --git a/dice/ext_dnd5e_template.go b/dice/ext_dnd5e_template.go
index 95ce5542..afd791fc 100644
--- a/dice/ext_dnd5e_template.go
+++ b/dice/ext_dnd5e_template.go
@@ -9,11 +9,11 @@ var _dnd5eTmpl = &GameSystemTemplate{
 	TemplateVer: "1.0",
 
 	SetConfig: SetConfig{
-		DiceExpr:   "d20",
-		DiceSides:  20,
-		Keys:       []string{"dnd", "dnd5e"},
-		EnableTip:  "已切换至20面骰，并自动开启dnd5e扩展",
-		RelatedExt: []string{"dnd5e"},
+		DiceSidesExpr: "20",
+		DiceSides:     20,
+		Keys:          []string{"dnd", "dnd5e"},
+		EnableTip:     "已切换至20面骰，并自动开启dnd5e扩展",
+		RelatedExt:    []string{"dnd5e"},
 	},
 
 	NameTemplate: map[string]NameTemplateItem{
@@ -35,8 +35,8 @@ var _dnd5eTmpl = &GameSystemTemplate{
 		"func abilityShowAs(base, buff) {" +
 		"  return `{base??0 + buff??0}{buff ? '[buff+'+ str(buff) + ']' }`" +
 		"}" +
-		"func pbCalc(base, factor, abMod) { // 基础值，熟练系数，属性调整值（Szz注：直接读取属性得到的是调整值）\n" +
-		"  return base + (abMod??0) + floor((熟练??0) * (factor??0));\n" +
+		"func pbCalc(base, factor, ab) { // 基础值，熟练系数，属性值\n" +
+		"  return base + (ab??0)/2 - 5 + floor((熟练??0) * (factor??0));\n" +
 		"}\n" +
 		"func abilityModifier(name) { // 计算调整值\n" +
 		"  return (load(name) ?? 0 + load('$buff_' + name) ?? 0) / 2 - 5\n" +
diff --git a/dice/ext_exp.go b/dice/ext_exp.go
index c7a47e15..677a2dbb 100644
--- a/dice/ext_exp.go
+++ b/dice/ext_exp.go
@@ -307,7 +307,7 @@ func cmdStReadOrMod(ctx *MsgContext, tmpl *GameSystemTemplate, text string) (r *
 	ctx.CreateVmIfNotExists()
 	vm := ctx.vm
 	vm.Config.DisableStmts = true
-	vm.Config.DefaultDiceSideExpr = "d" + strconv.FormatInt(getDefaultDicePoints(ctx), 10)
+	vm.Config.DefaultDiceSideExpr = strconv.FormatInt(getDefaultDicePoints(ctx), 10)
 
 	vm.Config.CallbackSt = func(_type string, name string, val *ds.VMValue, extra *ds.VMValue, op string, detail string) {
 		// fmt.Println("!!", _type, name, val, extra, op, detail)
diff --git a/dice/game_system_template.go b/dice/game_system_template.go
index ad6fd635..93b218c6 100644
--- a/dice/game_system_template.go
+++ b/dice/game_system_template.go
@@ -42,11 +42,11 @@ type NameTemplateItem struct {
 }
 
 type SetConfig struct {
-	RelatedExt []string `yaml:"relatedExt" json:"relatedExt"` // 关联扩展
-	DiceSides  int64    `yaml:"diceSides" json:"diceSides"`   // 骰子面数
-	DiceExpr   string   `yaml:"diceExpr" json:"diceExpr"`     // 基础骰表达式 可替代骰子面数
-	Keys       []string `yaml:"keys" json:"keys"`             // 可用于 .set xxx 的key
-	EnableTip  string   `yaml:"enableTip" json:"enableTip"`   // 启用提示
+	RelatedExt    []string `yaml:"relatedExt" json:"relatedExt"`       // 关联扩展
+	DiceSides     int64    `yaml:"diceSides" json:"diceSides"`         // 骰子面数
+	DiceSidesExpr string   `yaml:"diceSidesExpr" json:"diceSidesExpr"` // 骰子面数表达式
+	Keys          []string `yaml:"keys" json:"keys"`                   // 可用于 .set xxx 的key
+	EnableTip     string   `yaml:"enableTip" json:"enableTip"`         // 启用提示
 }
 
 type GameSystemTemplate struct {
@@ -234,7 +234,7 @@ func (t *GameSystemTemplate) GetShowAs(ctx *MsgContext, k string) (string, *ds.V
 }
 
 func (t *GameSystemTemplate) GetRealValue(ctx *MsgContext, k string) (*ds.VMValue, error) {
-	// 跟 showas 一样，但是不采用showas而是返回实际值1
+	// 跟 showas 一样，但是不采用showas而是返回实际值
 	// 显示本体
 	am := ctx.Dice.AttrsManager
 	curAttrs := lo.Must(am.LoadByCtx(ctx))
diff --git a/dice/rollvm_migrate.go b/dice/rollvm_migrate.go
index ae03f501..243b207d 100644
--- a/dice/rollvm_migrate.go
+++ b/dice/rollvm_migrate.go
@@ -243,7 +243,7 @@ func (a spanByEnd) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a spanByEnd) Less(i, j int) bool { return a[i].End < a[j].End }
 
 func (ctx *MsgContext) setCocPrefixReadForVM(cb func(cocFlagVarPrefix string)) {
-	ctx.vm.Config.HookFuncValueLoad = func(name string) (string, *ds.VMValue) {
+	ctx.vm.Config.HookFuncValueLoad = func(ctx *ds.Context, name string) (string, *ds.VMValue) {
 		re := regexp.MustCompile(`^(困难|极难|大成功|常规|失败|困難|極難|常規|失敗)?([^\d]+)(\d+)?$`)
 		m := re.FindStringSubmatch(name)
 
@@ -287,19 +287,29 @@ func tryLoadByBuff(ctx *MsgContext, varname string, curVal *ds.VMValue) *ds.VMVa
 // setDndReadForVM 主要是为rc设定属性豁免，暂时没有写在规则模板的原因是可以自定义detail输出
 func (ctx *MsgContext) setDndReadForVM(rcMode bool) {
 	var skip bool
-	ctx.vm.Config.HookFuncValueLoadOverwrite = func(varname string, curVal *ds.VMValue, detail *ds.BufferSpan) *ds.VMValue {
-		// if !skip {
-		// curVal = tryLoadByBuff(ctx, varname, curVal)
-		// }
-		if !skip && rcMode && isAbilityScores(varname) {
-			if curVal != nil && curVal.TypeId == ds.VMTypeInt {
-				curVal = tryLoadByBuff(ctx, varname, curVal)
-				mod := curVal.MustReadInt()/2 - 5
-				if detail != nil {
+	ctx.vm.Config.HookFuncValueLoadOverwrite = func(vm *ds.Context, varname string, curVal *ds.VMValue, detail *ds.BufferSpan) *ds.VMValue {
+		if !skip && rcMode {
+			// rc时将属性替换为调整值，只在0级起作用，避免在函数调用等地方造成影响
+			if isAbilityScores(varname) && vm.Depth() == 0 && vm.UpCtx == nil {
+				if curVal != nil && curVal.TypeId == ds.VMTypeInt {
+					curVal = tryLoadByBuff(ctx, varname, curVal)
+					mod := curVal.MustReadInt()/2 - 5
+					if detail != nil {
+						detail.Tag = "dnd-rc"
+						detail.Text = fmt.Sprintf("%s调整值%d", varname, mod)
+					}
+					return ds.NewIntVal(mod)
+				}
+			} else if dndAttrParent[varname] != "" && curVal.TypeId == ds.VMTypeInt {
+				name := dndAttrParent[varname]
+				base, err := ctx.SystemTemplate.GetRealValue(ctx, name)
+				if err == nil {
+					ab := tryLoadByBuff(ctx, name, base)
+					mod := ab.MustReadInt()/2 - 5
+
 					detail.Tag = "dnd-rc"
-					detail.Text = fmt.Sprintf("%s调整值%d", varname, mod)
+					detail.Text = fmt.Sprintf("%s调整值%d", name, mod)
 				}
-				return ds.NewIntVal(mod)
 			}
 		}
 
@@ -338,6 +348,7 @@ func (ctx *MsgContext) setDndReadForVM(rcMode bool) {
 			}
 			return ret
 		}
+
 		return curVal
 	}
 }
@@ -425,7 +436,7 @@ func (ctx *MsgContext) CreateVmIfNotExists() {
 			}
 
 			// 从模板取值，模板中的设定是如果取不到获得0
-			// TODO: 目前没有好的方法去复制ctx，实际上这个行为应当类似于ds中的函数调用
+			// TODO: 目前没有好的方法去复制vm，实际上这个行为应当类似于ds中的函数调用
 			ctx.CreateVmIfNotExists()
 			ctx2 := *ctx
 			ctx2.vm = nil
@@ -512,7 +523,12 @@ func (ctx *MsgContext) CreateVmIfNotExists() {
 				}
 			}
 
-			exprText := string(detailResult[item.begin:item.end])
+			exprText := last.Expr
+			baseExprText := string(detailResult[item.begin:item.end])
+			if last.Expr == "" {
+				exprText = baseExprText
+			}
+
 			writeBuf(detailResult[:item.begin])
 
 			// 主体结果部分，如 (10d3)d5=63[(10d3)d5=2+2+2+5+2+5+5+4+1+3+4+1+4+5+4+3+4+5+2,10d3=19]
@@ -536,7 +552,7 @@ func (ctx *MsgContext) CreateVmIfNotExists() {
 			}
 
 			detail += subDetailsText + "]"
-			if len(m) == 1 && detail == "["+exprText+"]" {
+			if len(m) == 1 && detail == "["+baseExprText+"]" {
 				detail = "" // 规则1.3
 			}
 			if len(detail) > 400 {
@@ -563,7 +579,7 @@ func (ctx *MsgContext) CreateVmIfNotExists() {
 		// 情况不明，在sealchat的第一次测试中出现Group为nil
 		ctx.vm.Config.DefaultDiceSideExpr = fmt.Sprintf("%d", ctx.Group.DiceSideNum)
 	} else {
-		ctx.vm.Config.DefaultDiceSideExpr = "d100"
+		ctx.vm.Config.DefaultDiceSideExpr = "100"
 	}
 }
 
diff --git a/go.mod b/go.mod
index 13aaefa7..cea94c43 100644
--- a/go.mod
+++ b/go.mod
@@ -25,6 +25,7 @@ require (
 	github.com/go-creed/sat v1.0.3
 	github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
 	github.com/golang-module/carbon v1.7.3
+	github.com/gonutz/w32/v2 v2.11.1
 	github.com/google/uuid v1.6.0
 	github.com/gorilla/websocket v1.5.1
 	github.com/grokify/html-strip-tags-go v0.0.1
@@ -54,7 +55,7 @@ require (
 	github.com/samber/lo v1.44.0
 	github.com/schollz/progressbar/v3 v3.14.4
 	github.com/sealdice/botgo v0.0.0-20240102160217-e61d5bdfe083
-	github.com/sealdice/dicescript v0.0.0-20240623065436-0e5e379b722e
+	github.com/sealdice/dicescript v0.0.0-20240702175100-960b24bd2054
 	github.com/slack-go/slack v0.13.0
 	github.com/sunshineplan/imgconv v1.1.4
 	github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
diff --git a/go.sum b/go.sum
index 1ce571e0..bd8387b9 100644
--- a/go.sum
+++ b/go.sum
@@ -175,6 +175,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gonutz/w32/v2 v2.11.1 h1:plG738ZY7VIkPGf3adZ6lFeAf2evCKrULKyZT5GrPoc=
+github.com/gonutz/w32/v2 v2.11.1/go.mod h1:MgtHx0AScDVNKyB+kjyPder4xIi3XAcHS6LDDU2DmdE=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -335,8 +337,8 @@ github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9d
 github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI=
 github.com/sealdice/botgo v0.0.0-20240102160217-e61d5bdfe083 h1:s/jzCGYlM/0+TYTXwva5574EFnIv/ggPCoXHFpdbSUw=
 github.com/sealdice/botgo v0.0.0-20240102160217-e61d5bdfe083/go.mod h1:MGtR0REQDslBwQE+Rln4P9iDjH/ZInlu5qzOLdvBWJU=
-github.com/sealdice/dicescript v0.0.0-20240623065436-0e5e379b722e h1:2apGNWBOR6FzYTLgPzdaGjuTlx+okdV5N3Hplqu7lkM=
-github.com/sealdice/dicescript v0.0.0-20240623065436-0e5e379b722e/go.mod h1:uof752qJvEQ4Kze+NVag+RKGgj5C4K3kMHoK3e2vOLg=
+github.com/sealdice/dicescript v0.0.0-20240702175100-960b24bd2054 h1:/x8mmT4MBPS1V32BrVc8yEXSn6XQ6H/4c1qCurN/tjo=
+github.com/sealdice/dicescript v0.0.0-20240702175100-960b24bd2054/go.mod h1:uof752qJvEQ4Kze+NVag+RKGgj5C4K3kMHoK3e2vOLg=
 github.com/sealdice/kook v0.0.3 h1:STMtiKRMFjhSFmUxi0BU5ktNkCQ8qi7Y5EEfrmYKvWY=
 github.com/sealdice/kook v0.0.3/go.mod h1:WjHC7AmbmNjInT/U/etBVOmAw7T6EqdCwApceRGs1sk=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=


Test patch:
diff --git a/.github/workflows/test_and_lint.yml b/.github/workflows/test_and_lint.yml
index 7ff23877..a2f3f22a 100644
--- a/.github/workflows/test_and_lint.yml
+++ b/.github/workflows/test_and_lint.yml
@@ -26,7 +26,7 @@ jobs:
       - run: go get
       - run: go generate ./...
 
-      - run: go test -v -race ./...
+      - run: go test -v ./...
       - run: go vet ./...
 
       - name: GolangCI-Lint