服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
197 lines
6.0 KiB
Go
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
|
|
}
|