服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
253 lines
9.5 KiB
Go
253 lines
9.5 KiB
Go
package services
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"unicode/utf16"
|
|
|
|
"pvfine/internal/pvf"
|
|
)
|
|
|
|
const upgradeTestRow = "2 0.018 0.1 0.033 0.3 0.6 12500 2 3 3037 10 1 0.018 0.1 0.033 0.17 0.6"
|
|
|
|
// Representative 24-column rows from the supplied normal table: +1, +11, +18.
|
|
const upgradeTypedRow = "2.0 0 0 0 0 0.6 0 0.828 0 0 1 0 1 0 3037 10 0.3 0.018 0.1 0.033 0.17 0.228 0 0.2622"
|
|
const upgradeTypedDowngradeRow = "26.9 0 0 0 0 6.75 0 9.703125 75000 75000 2 3 3 0 3037 120 3 0.22 1.9 0.645 3.17 2.565 0 2.94975"
|
|
const upgradeTypedDifferentRatesRow = "74.3 0 0 0 0 63 0 90.5625 90000 88000 3 0 3 0 3037 260 8.2 0.62 10.3 3.501 17.17 23.94 0 27.531"
|
|
|
|
func TestUpgradeTypedRowsAndIndependentFields(t *testing.T) {
|
|
text := "# 😀\r\n[table]\r\n[table type]\r\n`event` `unrelated`\r\n[/table]\r\n" +
|
|
"[table]\r\n[table type]\r\n`normal` " + upgradeTypedRow + "\r\n" + upgradeTypedDowngradeRow + "\r\n" + upgradeTypedDifferentRatesRow + "\r\n[/table]\r\n" +
|
|
"[table]\r\n[table type]\r\n`amplify` " + strings.Replace(upgradeTypedRow, "3037", "3038", 1) + "\r\n[/table]"
|
|
doc, err := parseUpgrade(text, "normal")
|
|
if err != nil || len(doc.Rows) != 3 {
|
|
t.Fatalf("normal: %v %v", doc, err)
|
|
}
|
|
first, downgrade, different := doc.Rows[0], doc.Rows[1], doc.Rows[2]
|
|
if first.Fields[3].Value != 3037 || first.Fields[4].Value != 10 {
|
|
t.Fatal("incorrect material fields")
|
|
}
|
|
if downgrade.Fields[1].Value != 2 || downgrade.Fields[2].Value != 3 || downgrade.FieldsT2[1].Value != 3 || downgrade.FieldsT2[2].Value != 0 {
|
|
t.Fatal("T1/T2 operations mixed")
|
|
}
|
|
if different.Fields[0].Value != 90000 || different.FieldsT2[0].Value != 88000 {
|
|
t.Fatal("T1/T2 probabilities mixed")
|
|
}
|
|
source := utf16.Encode([]rune(strings.ReplaceAll(text, "\r\n", "\n")))
|
|
for _, row := range doc.Rows {
|
|
for _, field := range append(append([]SkillNumber(nil), row.Fields...), row.FieldsT2...) {
|
|
if got := string(utf16.Decode(source[field.Start:field.End])); got != strconv.FormatInt(int64(field.Value), 10) {
|
|
t.Fatalf("wrong field position: %s", got)
|
|
}
|
|
}
|
|
}
|
|
amplify, err := parseUpgrade(text, "amplify")
|
|
if err != nil || len(amplify.Rows) != 1 || amplify.Rows[0].Fields[3].Value != 3038 {
|
|
t.Fatalf("amplify: %v %v", amplify, err)
|
|
}
|
|
large, err := parseUpgrade("[table]\n[table type]\n`normal` "+strings.Repeat(upgradeTypedRow+"\n", 250)+"[/table]", "normal")
|
|
if err != nil || len(large.Rows) != 250 || large.Rows[249].Level != 250 {
|
|
t.Fatal("typed levels truncated")
|
|
}
|
|
if _, err := parseUpgrade("[table]\n[table type]\n`normal` "+upgradeTestRow+"\n[/table]", "normal"); err == nil {
|
|
t.Fatal("accepted wrong typed row width")
|
|
}
|
|
}
|
|
|
|
func TestUpgradeParseSourceAndRange(t *testing.T) {
|
|
text := "#PVF_File\r\n[name]\r\n`😀`\r\n[table]\r\n" + strings.Repeat(upgradeTestRow+"\r\n", 32) + "[/table]\r\n[other]\r\n99"
|
|
doc, err := parseUpgrade(text, "normal")
|
|
if err != nil || len(doc.Rows) != 31 {
|
|
t.Fatalf("rows: %v %v", doc, err)
|
|
}
|
|
row := doc.Rows[0]
|
|
if row.Level != 1 || row.Fields[0].Value != 12500 || row.Fields[3].Value != 3037 || row.Fields[4].Value != 10 {
|
|
t.Fatalf("fields: %+v", row)
|
|
}
|
|
source := utf16.Encode([]rune(strings.ReplaceAll(text, "\r\n", "\n")))
|
|
field := row.Fields[0]
|
|
if string(utf16.Decode(source[field.Start:field.End])) != "12500" {
|
|
t.Fatal("invalid UTF-16 source positions")
|
|
}
|
|
}
|
|
|
|
func TestUpgradeRejectMalformed(t *testing.T) {
|
|
for _, text := range []string{
|
|
"[other]\n1", "[table]\n", "[table]\n1 2",
|
|
"[table]\n" + upgradeTestRow + "\n[table]\n" + upgradeTestRow,
|
|
"[table]\n" + strings.Replace(upgradeTestRow, "12500", "100001", 1),
|
|
"[table]\n" + strings.Replace(upgradeTestRow, "12500", "1.0", 1),
|
|
"[table]\n" + strings.Replace(upgradeTestRow, "3037", "-1", 1),
|
|
"[table]\n" + strings.Replace(upgradeTestRow, "12500 2 3", "12500 4 3", 1),
|
|
} {
|
|
if _, err := parseUpgrade(text, "normal"); err == nil {
|
|
t.Fatalf("accepted malformed data: %q", text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpgradeReadDraftAndPaths(t *testing.T) {
|
|
a := pvf.New()
|
|
text := "[table]\n" + upgradeTestRow + "\n[/table]"
|
|
indexes := []int32{}
|
|
for _, path := range []string{"etc/upgrade.etc", "etc/amplifyupgrade.etc", "etc/other.etc"} {
|
|
i, err := a.AddFileText(path, text, pvf.TypeScript)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
indexes = append(indexes, i)
|
|
}
|
|
c := NewCore()
|
|
if err := c.setArchive(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(c.closeArchive)
|
|
s := NewFileGUIService(c)
|
|
draft := strings.Replace(text, "12500", "0", 1)
|
|
for _, i := range indexes[:2] {
|
|
doc, err := s.ReadUpgrade(i, draft)
|
|
if err != nil || doc.Rows[0].Fields[0].Value != 0 {
|
|
t.Fatalf("draft: %v %v", doc, err)
|
|
}
|
|
original, _ := a.Text(i)
|
|
if !strings.Contains(original, "12500") {
|
|
t.Fatal("read mutated archive")
|
|
}
|
|
}
|
|
if _, err := s.ReadUpgrade(indexes[2], draft); err == nil {
|
|
t.Fatal("accepted unrelated path")
|
|
}
|
|
if _, err := s.ReadUpgrade(-1, draft); err == nil {
|
|
t.Fatal("accepted invalid index")
|
|
}
|
|
// Both documents may contain both types; choose by the file being edited.
|
|
typedDraft := "[table]\n[table type]\n`normal` " + upgradeTypedRow + "\n[/table]\n" +
|
|
"[table]\n[table type]\n`amplify` " + strings.Replace(upgradeTypedRow, "3037", "3038", 1) + "\n[/table]"
|
|
for n, i := range indexes[:2] {
|
|
doc, err := s.ReadUpgrade(i, typedDraft)
|
|
if err != nil || doc.Rows[0].Fields[3].Value != float64(3037+n) {
|
|
t.Fatalf("file type selection: %v %v", doc, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpgradeSelectTypedTables(t *testing.T) {
|
|
text := "# 😀\r\n[table]\r\n[table type]\r\n`event` `other data`\r\n[/table]\r\n[table]\r\n[table type]\r\n`normal` 2.0 20000 3037\r\n[/table]\r\n[table]\r\n[table type]\r\n`amplify` 4.0 40000 3038\r\n[/table]"
|
|
for _, kind := range []string{"normal", "amplify"} {
|
|
values, typed, err := upgradeTableTokens(text, kind)
|
|
if err != nil || !typed || len(values) != 3 {
|
|
t.Fatalf("%s: %v %v", kind, values, err)
|
|
}
|
|
want := "20000"
|
|
if kind == "amplify" {
|
|
want = "40000"
|
|
}
|
|
if values[1].Value != want {
|
|
t.Fatalf("wrong table selected: %v", values)
|
|
}
|
|
source := utf16.Encode([]rune(strings.ReplaceAll(text, "\r\n", "\n")))
|
|
if string(utf16.Decode(source[values[1].Start:values[1].End])) != want {
|
|
t.Fatal("source offsets lost")
|
|
}
|
|
}
|
|
for _, candidate := range []string{
|
|
"[table]\n[table type]\n`event` 1\n[/table]",
|
|
"[table]\n[table type]\n`normal` 1\n[/table]\n[table]\n[table type]\n`normal` 2\n[/table]",
|
|
} {
|
|
if _, _, err := upgradeTableTokens(candidate, "normal"); err == nil {
|
|
t.Fatal("accepted missing or ambiguous target")
|
|
}
|
|
}
|
|
}
|
|
|
|
const upgrade19Row1 = "2.2 0.018 0.1 0.033 0.2 0.6 0 0 1 0 3037 10 1 0.018 0.1 0.033 0.17 0.228 0"
|
|
const upgrade19Row5 = "7.7 0.128 0.7 0.233 1.2 1.8 0 10000 2 1 3037 50 5 0.09 0.5 0.169 0.83 0.684 0"
|
|
const upgrade19Row11 = "12.1 0.22 1.2 0.4 2 2.7 0 70000 3 0 3037 120 11 0.144 0.8 0.27 1.33 1.026 0"
|
|
|
|
func TestUpgrade19ColumnsRowsAndFields(t *testing.T) {
|
|
text := "# 😀\r\n[table]\r\n[table type]\r\n`normal`\r\n" + upgrade19Row1 + "\r\n" + upgrade19Row5 + "\r\n" + upgrade19Row11 + "\r\n[/table]"
|
|
doc, err := parseUpgrade(text, "normal")
|
|
if err != nil || len(doc.Rows) != 3 {
|
|
t.Fatalf("parseUpgrade 19 columns: %v %v", doc, err)
|
|
}
|
|
r1, r5, r11 := doc.Rows[0], doc.Rows[1], doc.Rows[2]
|
|
|
|
if r1.Level != 1 || r1.Fields[0].Value != 0 || r1.Fields[1].Value != 1 || r1.Fields[2].Value != 0 || r1.Fields[3].Value != 3037 || r1.Fields[4].Value != 10 {
|
|
t.Fatalf("row 1 fields wrong: %+v", r1)
|
|
}
|
|
if len(r1.FieldsT2) != 0 {
|
|
t.Fatalf("row 1 should not have FieldsT2: %+v", r1.FieldsT2)
|
|
}
|
|
|
|
if r5.Level != 2 || r5.Fields[0].Value != 10000 || r5.Fields[1].Value != 2 || r5.Fields[2].Value != 1 || r5.Fields[3].Value != 3037 || r5.Fields[4].Value != 50 {
|
|
t.Fatalf("row 5 fields wrong: %+v", r5)
|
|
}
|
|
|
|
if r11.Level != 3 || r11.Fields[0].Value != 70000 || r11.Fields[1].Value != 3 || r11.Fields[2].Value != 0 || r11.Fields[4].Value != 120 {
|
|
t.Fatalf("row 11 fields wrong: %+v", r11)
|
|
}
|
|
|
|
source := utf16.Encode([]rune(strings.ReplaceAll(text, "\r\n", "\n")))
|
|
for _, row := range doc.Rows {
|
|
for _, field := range row.Fields {
|
|
if got := string(utf16.Decode(source[field.Start:field.End])); got != strconv.FormatInt(int64(field.Value), 10) {
|
|
t.Fatalf("wrong field position: %s, want %d", got, int64(field.Value))
|
|
}
|
|
}
|
|
}
|
|
|
|
large19, err := parseUpgrade("[table]\n[table type]\n`normal` "+strings.Repeat(upgrade19Row1+"\n", 255)+"[/table]", "normal")
|
|
if err != nil || len(large19.Rows) != 255 || large19.Rows[254].Level != 255 {
|
|
t.Fatalf("19 column 255 levels truncated: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUpgradeRealArchive86JP(t *testing.T) {
|
|
testFile := os.Getenv("PVF_TESTFILE")
|
|
if testFile == "" {
|
|
t.Skip("PVF_TESTFILE not set")
|
|
}
|
|
a, err := pvf.Open(testFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer a.Release()
|
|
|
|
core := NewCore()
|
|
if err := core.setArchive(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer core.closeArchive()
|
|
|
|
gui := NewFileGUIService(core)
|
|
|
|
for _, path := range []string{"etc/upgrade.etc", "etc/amplifyupgrade.etc"} {
|
|
idx, ok := a.Find(path)
|
|
if !ok {
|
|
t.Logf("archive does not have %s", path)
|
|
continue
|
|
}
|
|
text, err := a.Text(idx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
doc, err := gui.ReadUpgrade(idx, text)
|
|
if err != nil {
|
|
t.Fatalf("ReadUpgrade %s failed: %v", path, err)
|
|
}
|
|
if len(doc.Rows) != 255 {
|
|
t.Fatalf("%s rows = %d, want 255", path, len(doc.Rows))
|
|
}
|
|
if doc.Rows[0].Fields[3].Value <= 0 {
|
|
t.Fatalf("%s material ID invalid: %v", path, doc.Rows[0].Fields[3].Value)
|
|
}
|
|
if doc.Rows[0].CostName == "" {
|
|
t.Logf("%s note: costName empty (no annotation database or unknown item)", path)
|
|
} else {
|
|
t.Logf("%s item 1 costName: %s", path, doc.Rows[0].CostName)
|
|
}
|
|
}
|
|
}
|