DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,191 @@
|
||||
package script
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"pvfine/internal/pvf"
|
||||
)
|
||||
|
||||
// editUpgrade evaluates every callback before staging one complete edit.
|
||||
func (b *gojaBindings) editUpgrade(file *FileHandle, call goja.FunctionCall) (goja.Value, error) {
|
||||
options, err := objectArgument(call.Argument(0))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options == nil {
|
||||
return nil, fmt.Errorf("editUpgrade 需要 options")
|
||||
}
|
||||
for _, key := range options.Keys() {
|
||||
switch key {
|
||||
case "equipment", "fromLevel", "toLevel", "successRate", "costCount":
|
||||
default:
|
||||
return nil, fmt.Errorf("未知强化选项 %s", key)
|
||||
}
|
||||
}
|
||||
path := strings.ToLower(strings.ReplaceAll(file.Path(), "\\", "/"))
|
||||
kind := "normal"
|
||||
if path == "etc/amplifyupgrade.etc" {
|
||||
kind = "amplify"
|
||||
} else if path != "etc/upgrade.etc" {
|
||||
return nil, fmt.Errorf("editUpgrade 仅支持强化/增幅文件")
|
||||
}
|
||||
equipment := "weapon"
|
||||
if !isMissingValue(options.Get("equipment")) {
|
||||
equipment, err = requiredString(options.Get("equipment"), "装备类型")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if equipment != "weapon" && equipment != "armor" {
|
||||
return nil, fmt.Errorf("equipment 必须是 weapon 或 armor")
|
||||
}
|
||||
text, err := file.Text()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokens, typed, err := pvf.SelectUpgradeTableTokens(text, kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout, err := pvf.DetectUpgradeTableLayout(tokens, typed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if equipment == "armor" && !layout.HasT2 {
|
||||
return nil, fmt.Errorf("旧版表格没有独立防具数据")
|
||||
}
|
||||
rateColumn := layout.RateColumn
|
||||
if equipment == "armor" {
|
||||
rateColumn = layout.ColumnsT2[0]
|
||||
}
|
||||
countColumn := layout.CountColumn
|
||||
width := layout.Width
|
||||
for _, token := range tokens {
|
||||
if token.TokenType != 0 && token.TokenType != 2 {
|
||||
return nil, fmt.Errorf("表格包含非数值数据")
|
||||
}
|
||||
}
|
||||
maximum := len(tokens) / width
|
||||
if !typed && maximum > 31 {
|
||||
maximum = 31
|
||||
}
|
||||
from, err := optionInteger(options, "fromLevel", 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
to, err := optionInteger(options, "toLevel", int64(maximum))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if from < 1 || to < from || to > int64(maximum) {
|
||||
return nil, fmt.Errorf("等级范围必须位于 1~%d", maximum)
|
||||
}
|
||||
type update struct {
|
||||
name string
|
||||
column int
|
||||
value goja.Value
|
||||
}
|
||||
updates := []update{}
|
||||
for _, item := range []update{{"successRate", rateColumn, options.Get("successRate")}, {"costCount", countColumn, options.Get("costCount")}} {
|
||||
if !isMissingValue(item.value) {
|
||||
updates = append(updates, item)
|
||||
}
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return nil, fmt.Errorf("请提供 successRate 或 costCount")
|
||||
}
|
||||
type patch struct {
|
||||
start, end int
|
||||
value string
|
||||
}
|
||||
patches := []patch{}
|
||||
offsets := upgradeByteOffsets(text)
|
||||
for level := int(from); level <= int(to); level++ {
|
||||
if err := b.host.checkContext(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range updates {
|
||||
token := tokens[(level-1)*width+item.column]
|
||||
current, err := strconv.ParseFloat(token.Value, 64)
|
||||
if err != nil || token.TokenType != 0 {
|
||||
return nil, fmt.Errorf("第 %d 级 %s 必须是整数", level, item.name)
|
||||
}
|
||||
if item.name == "successRate" {
|
||||
if current < 0 || current > 100000 {
|
||||
return nil, fmt.Errorf("原始失败概率超出范围")
|
||||
}
|
||||
current = 100 - current/1000
|
||||
}
|
||||
value := item.value
|
||||
if fn, ok := goja.AssertFunction(value); ok {
|
||||
value, err = fn(goja.Undefined(), b.vm.ToValue(level), b.vm.ToValue(current))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
switch value.Export().(type) {
|
||||
case int64, float64, int, int32, float32:
|
||||
default:
|
||||
return nil, fmt.Errorf("第 %d 级 %s 计算结果必须是数字", level, item.name)
|
||||
}
|
||||
number := value.ToFloat()
|
||||
if math.IsNaN(number) || math.IsInf(number, 0) || number < 0 {
|
||||
return nil, fmt.Errorf("第 %d 级 %s 计算结果无效", level, item.name)
|
||||
}
|
||||
if item.name == "successRate" {
|
||||
if number > 100 {
|
||||
return nil, fmt.Errorf("成功率必须位于 0~100")
|
||||
}
|
||||
number = math.Round((100 - number) * 1000)
|
||||
} else if number > math.MaxInt32 || math.Trunc(number) != number {
|
||||
return nil, fmt.Errorf("消耗数量必须是非负 32 位整数")
|
||||
}
|
||||
if token.Start < 0 || token.End >= len(offsets) {
|
||||
return nil, fmt.Errorf("表格位置无效")
|
||||
}
|
||||
patches = append(patches, patch{offsets[token.Start], offsets[token.End], strconv.FormatInt(int64(number), 10)})
|
||||
}
|
||||
}
|
||||
sort.Slice(patches, func(i, j int) bool { return patches[i].start > patches[j].start })
|
||||
updated := text
|
||||
for _, patch := range patches {
|
||||
updated = updated[:patch.start] + patch.value + updated[patch.end:]
|
||||
}
|
||||
latest, err := file.Text()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if latest != text {
|
||||
return nil, fmt.Errorf("计算期间文件发生变化,已拒绝覆盖")
|
||||
}
|
||||
if updated != text {
|
||||
if err := file.SetText(updated); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return b.vm.ToValue(to - from + 1), nil
|
||||
}
|
||||
|
||||
// Translate normalized UTF-16 token offsets to original UTF-8 byte offsets,
|
||||
// retaining CRLF and lone CR line endings in source patches.
|
||||
func upgradeByteOffsets(text string) []int {
|
||||
offsets := []int{0}
|
||||
for i := 0; i < len(text); {
|
||||
r, size := utf8.DecodeRuneInString(text[i:])
|
||||
if r == '\r' && i+size < len(text) && text[i+size] == '\n' {
|
||||
size++
|
||||
}
|
||||
if r > 0xffff {
|
||||
offsets = append(offsets, i)
|
||||
}
|
||||
i += size
|
||||
offsets = append(offsets, i)
|
||||
}
|
||||
return offsets
|
||||
}
|
||||
Reference in new issue
Block a user