服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
192 lines
6.3 KiB
Go
192 lines
6.3 KiB
Go
package services
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
"strconv"
|
||
|
||
"pvfine/internal/pvf"
|
||
)
|
||
|
||
// UpgradeRow exposes only the five editable columns. Level follows the source
|
||
// tool's row order. Fields holds weapon settings and shared materials;
|
||
// FieldsT2 holds armor probability, failure operation, and downgrade parameter.
|
||
// All other table parameters retain their source text.
|
||
type UpgradeRow struct {
|
||
Level int `json:"level"`
|
||
Fields []SkillNumber `json:"fields"`
|
||
FieldsT2 []SkillNumber `json:"fieldsT2"`
|
||
CostName string `json:"costName"`
|
||
}
|
||
|
||
type UpgradeDocument struct {
|
||
Rows []UpgradeRow `json:"rows"`
|
||
SafeRows []SafeUpgradeRow `json:"safeRows"`
|
||
SafeIssue string `json:"safeIssue"`
|
||
}
|
||
|
||
// SafeUpgradeRow preserves eight source fields. Column 2 is failure probability
|
||
// with denominator 100000; 3 is correction, 5/6 are material ID/count, 7 is gold.
|
||
// Columns 1/4 remain unidentified and are preserved by GUI edits.
|
||
type SafeUpgradeRow struct {
|
||
Fields []SkillNumber `json:"fields"`
|
||
CostName string `json:"costName"`
|
||
}
|
||
|
||
func isUpgradePath(path string) bool {
|
||
return sameSearchPath(path, "etc/upgrade.etc") || sameSearchPath(path, "etc/amplifyupgrade.etc")
|
||
}
|
||
|
||
func (s *FileGUIService) ReadUpgrade(fileIndex int32, text string) (*UpgradeDocument, error) {
|
||
s.c.mu.Lock()
|
||
defer s.c.mu.Unlock()
|
||
a := s.c.archive
|
||
if a == nil {
|
||
return nil, ErrNoArchive
|
||
}
|
||
if err := validateAnnotationIndex(a, fileIndex); err != nil {
|
||
return nil, err
|
||
}
|
||
if !isUpgradePath(a.Path(fileIndex)) {
|
||
return nil, fmt.Errorf("强化/增幅 GUI 仅支持 etc/upgrade.etc 和 etc/amplifyupgrade.etc")
|
||
}
|
||
if len(text) > int(s.c.editableByteLimit()) {
|
||
return nil, fmt.Errorf("强化/增幅文本过大")
|
||
}
|
||
tableType := "normal"
|
||
if sameSearchPath(a.Path(fileIndex), "etc/amplifyupgrade.etc") {
|
||
tableType = "amplify"
|
||
}
|
||
doc, err := parseUpgrade(text, tableType)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
names := make(map[string]string)
|
||
resolveCostName := func(value float64) string {
|
||
id := strconv.FormatInt(int64(value), 10)
|
||
if name, ok := names[id]; ok {
|
||
return name
|
||
}
|
||
name := ""
|
||
if s.c.annotationEngine != nil {
|
||
if ref, ok := s.c.resolveAnnotationReferenceLocked("物品", id); ok {
|
||
name = resolvePreviewText(a, ref.Name)
|
||
}
|
||
}
|
||
names[id] = name
|
||
return name
|
||
}
|
||
for i := range doc.Rows {
|
||
row := &doc.Rows[i]
|
||
row.CostName = resolveCostName(row.Fields[3].Value)
|
||
}
|
||
for i := range doc.SafeRows {
|
||
row := &doc.SafeRows[i]
|
||
row.CostName = resolveCostName(row.Fields[5].Value)
|
||
}
|
||
return doc, nil
|
||
}
|
||
|
||
func upgradeTableTokens(text, tableType string) ([]pvf.ScriptElement, bool, error) {
|
||
return pvf.SelectUpgradeTableTokens(text, tableType)
|
||
}
|
||
|
||
func parseUpgrade(text, tableType string) (*UpgradeDocument, error) {
|
||
tokens, typed, err := upgradeTableTokens(text, tableType)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
layout, err := pvf.DetectUpgradeTableLayout(tokens, typed)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
values := []SkillNumber{}
|
||
for _, e := range tokens {
|
||
n, err := strconv.ParseFloat(e.Value, 64)
|
||
if err != nil || math.IsNaN(n) || math.IsInf(n, 0) || (e.TokenType != 0 && e.TokenType != 2) {
|
||
return nil, fmt.Errorf("[table] 包含非数值数据")
|
||
}
|
||
values = append(values, SkillNumber{Value: n, Start: e.Start, End: e.End, TokenType: e.TokenType})
|
||
}
|
||
width := layout.Width
|
||
columns := layout.Columns
|
||
doc := &UpgradeDocument{Rows: []UpgradeRow{}}
|
||
doc.SafeRows, err = parseSafeUpgrade(text)
|
||
if err != nil {
|
||
doc.SafeIssue = err.Error()
|
||
}
|
||
limit := len(values) / width
|
||
// The legacy editor exposes 31 rows. Typed tables expose every level.
|
||
if !typed && limit > 31 {
|
||
limit = 31
|
||
}
|
||
for level := 0; level < limit; level++ {
|
||
offset := level * width
|
||
fields := make([]SkillNumber, 0, 5)
|
||
for _, column := range columns {
|
||
fields = append(fields, values[offset+column])
|
||
}
|
||
row := UpgradeRow{Level: level + 1, Fields: fields, FieldsT2: []SkillNumber{}}
|
||
if layout.HasT2 {
|
||
for _, column := range layout.ColumnsT2 {
|
||
row.FieldsT2 = append(row.FieldsT2, values[offset+column])
|
||
}
|
||
}
|
||
allFields := append(append([]SkillNumber(nil), row.Fields...), row.FieldsT2...)
|
||
for _, field := range allFields {
|
||
if field.TokenType != 0 || field.Value < 0 || field.Value > math.MaxInt32 {
|
||
return nil, fmt.Errorf("第 %d 级:可编辑字段必须是非负 32 位整数", level+1)
|
||
}
|
||
}
|
||
if fields[0].Value > 100000 || fields[1].Value < 1 || fields[1].Value > 3 || (!layout.HasT2 && fields[2].Value > 31) {
|
||
return nil, fmt.Errorf("第 %d 级:失败概率、失败操作或降级参数超出范围", level+1)
|
||
}
|
||
if layout.HasT2 && (row.FieldsT2[0].Value > 100000 || row.FieldsT2[1].Value < 1 || row.FieldsT2[1].Value > 3) {
|
||
return nil, fmt.Errorf("第 %d 级:防具失败概率或失败操作超出范围", level+1)
|
||
}
|
||
doc.Rows = append(doc.Rows, row)
|
||
}
|
||
return doc, nil
|
||
}
|
||
|
||
func parseSafeUpgrade(text string) ([]SafeUpgradeRow, error) {
|
||
rows := []SafeUpgradeRow{}
|
||
values := []SkillNumber{}
|
||
sections := 0
|
||
for _, e := range pvf.ParseScriptView(text).Elements {
|
||
if e.Kind == pvf.ScriptElementSection && len(e.SectionPath) > 1 && e.SectionPath[0] == "safe upgrade" {
|
||
return rows, fmt.Errorf("[safe upgrade] 包含未知子 section")
|
||
}
|
||
if e.Kind == pvf.ScriptElementSection && e.Section == "safe upgrade" {
|
||
sections++
|
||
if len(e.SectionPath) != 1 {
|
||
return rows, fmt.Errorf("[safe upgrade] 必须位于顶层")
|
||
}
|
||
}
|
||
if e.Kind != pvf.ScriptElementToken || e.Section != "safe upgrade" {
|
||
continue
|
||
}
|
||
n, err := strconv.ParseInt(e.Value, 10, 32)
|
||
if err != nil || e.TokenType != 0 {
|
||
return rows, fmt.Errorf("[safe upgrade] 字段必须是 32 位整数")
|
||
}
|
||
values = append(values, SkillNumber{Value: float64(n), Start: e.Start, End: e.End, TokenType: e.TokenType})
|
||
}
|
||
if sections == 0 {
|
||
return rows, nil
|
||
}
|
||
if sections != 1 {
|
||
return rows, fmt.Errorf("存在多个 [safe upgrade],无法确定数据结构")
|
||
}
|
||
if len(values) == 0 || len(values)%8 != 0 {
|
||
return rows, fmt.Errorf("[safe upgrade] 每条需要 8 项,当前共 %d 项", len(values))
|
||
}
|
||
for offset := 0; offset < len(values); offset += 8 {
|
||
if values[offset+2].Value < 0 || values[offset+2].Value > 100000 {
|
||
return []SafeUpgradeRow{}, fmt.Errorf("[safe upgrade] 第 %d 条失败率必须位于 0~100000", offset/8+1)
|
||
}
|
||
rows = append(rows, SafeUpgradeRow{Fields: append([]SkillNumber(nil), values[offset:offset+8]...)})
|
||
}
|
||
return rows, nil
|
||
}
|