Files
90CNServer/pvf/services/equipment_set_preview.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

197 lines
6.0 KiB
Go

package services
import (
"fmt"
"strconv"
"strings"
"pvfine/internal/pvf"
)
const equipmentPartSetListPath = "etc/equipmentpartset.etc"
// readEquipmentSetPreviewLocked uses the editor text for the current equipment
// and the archive's in-memory edits for files referenced by the set list.
func (s *PreviewService) readEquipmentSetPreviewLocked(equipmentText string, issues *[]PreviewIssue) *EquipmentSetPreviewDocument {
defer debugPhase("ParseEQU.part-set", "")()
setID, start := equipmentPartSetID(equipmentText)
if setID == "" {
return nil
}
a := s.c.archive
warn := func(message string) {
addPreviewIssue(issues, equipmentText, start, "warning", "part set index", message)
}
listIndex, ok := a.Find(equipmentPartSetListPath)
if !ok {
warn("找不到套装列表: " + equipmentPartSetListPath)
return nil
}
finishList := debugPhase("ParseEQU.part-set-list-text", equipmentPartSetListPath)
listText, err := s.previewRelatedTextLocked(listIndex)
finishList()
if err != nil {
warn("读取套装列表失败: " + err.Error())
return nil
}
finishFind := debugPhase("ParseEQU.part-set-list-parse", fmt.Sprintf("set=%s", setID))
values, ok := findEquipmentPartSet(listText, setID)
finishFind()
if !ok {
warn("套装列表中找不到 ID: " + setID)
return nil
}
if len(values) < 7 || (len(values)-3)%4 != 0 {
warn("套装记录缺少名称或完整的部位数据: " + setID)
return nil
}
name := resolvePreviewText(a, values[2])
parts := make([]string, 0, (len(values)-3)/4)
for i := 3; i < len(values); i += 4 {
parts = append(parts, resolvePreviewText(a, values[i]))
}
if name == "" {
warn("套装名称为空: " + setID)
return nil
}
for _, part := range parts {
if part == "" {
warn("套装部位名称为空: " + setID)
return nil
}
}
// Paths in equipmentpartset.etc are relative to equipment/, not etc/.
effectPath, valid := resolveListPath("equipment/equipment.lst", values[1])
if !valid {
warn(fmt.Sprintf("套装效果文件路径无效: %s", values[1]))
return nil
}
effectIndex, ok := a.Find(effectPath)
if !ok {
warn(fmt.Sprintf("找不到套装效果文件: %s", values[1]))
return nil
}
finishEffect := debugPhase("ParseEQU.part-set-effect", effectPath)
effectText, err := s.previewRelatedTextLocked(effectIndex)
if err != nil {
finishEffect()
warn("读取套装效果文件失败: " + err.Error())
return nil
}
abilities, ok := parseEquipmentSetAbilities(a, effectText)
finishEffect()
if !ok {
warn("套装效果文件缺少完整的件数或基础效果描述: " + values[1])
return nil
}
return &EquipmentSetPreviewDocument{Name: name, Parts: parts, Abilities: abilities}
}
func (s *PreviewService) previewRelatedTextLocked(index int32) (string, error) {
if text, ok := s.c.editorText[index]; ok {
return text, nil
}
return s.c.archive.Text(index)
}
func equipmentPartSetID(text string) (string, int) {
for _, element := range pvf.ParseScriptView(text).Elements {
if element.Kind == pvf.ScriptElementToken && element.Index == 0 &&
len(element.SectionPath) == 1 && strings.EqualFold(element.Section, "part set index") {
return strings.TrimSpace(element.Value), element.Start
}
}
return "", 0
}
func findEquipmentPartSet(text, setID string) ([]string, bool) {
view := pvf.ParseScriptView(text)
var values []string
flush := func() ([]string, bool) {
if len(values) > 0 && strings.TrimSpace(values[0]) == setID {
return values, true
}
return nil, false
}
sectionID := 0
for _, element := range view.Elements {
if element.Kind == pvf.ScriptElementSection && len(element.SectionPath) == 1 &&
strings.EqualFold(element.Section, "equipment part set") {
if match, ok := flush(); ok {
return match, true
}
sectionID = element.SectionID
values = nil
continue
}
if element.Kind == pvf.ScriptElementToken && element.SectionID == sectionID && sectionID != 0 {
values = append(values, element.Value)
}
}
return flush()
}
func parseEquipmentSetAbilities(a *pvf.Archive, text string) ([]EquipmentSetAbility, bool) {
type abilityFields struct {
count []string
basic []string
detail []string
}
view := pvf.ParseScriptView(text)
fields := make([]abilityFields, 0)
sectionKinds := make(map[int]string)
current := -1
for _, element := range view.Elements {
if element.Kind == pvf.ScriptElementSection {
if len(element.SectionPath) == 1 && strings.EqualFold(element.Section, "piece set ability") {
fields = append(fields, abilityFields{})
current = len(fields) - 1
sectionKinds[element.SectionID] = "count"
} else if current >= 0 && len(element.SectionPath) == 2 &&
strings.EqualFold(element.SectionPath[0], "piece set ability") {
switch {
case strings.EqualFold(element.Section, "parameter basic explain"):
sectionKinds[element.SectionID] = "basic"
case strings.EqualFold(element.Section, "parameter detail explain"):
sectionKinds[element.SectionID] = "detail"
}
}
continue
}
if element.Kind != pvf.ScriptElementToken || current < 0 {
continue
}
switch sectionKinds[element.SectionID] {
case "count":
fields[current].count = append(fields[current].count, element.Value)
case "basic":
fields[current].basic = append(fields[current].basic, element.Value)
case "detail":
fields[current].detail = append(fields[current].detail, element.Value)
}
}
if len(fields) == 0 {
return nil, false
}
abilities := make([]EquipmentSetAbility, 0, len(fields))
for _, field := range fields {
if len(field.count) != 1 {
return nil, false
}
pieces, err := strconv.ParseInt(strings.TrimSpace(field.count[0]), 10, 32)
if err != nil || pieces <= 0 {
return nil, false
}
base := normalizeExplain(resolvePreviewText(a, joinFieldValues(field.basic)))
if base == "" {
return nil, false
}
abilities = append(abilities, EquipmentSetAbility{
Pieces: int32(pieces),
BaseExplain: base,
DetailExplain: normalizeExplain(resolvePreviewText(a, joinFieldValues(field.detail))),
})
}
return abilities, true
}