服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
679 lines
21 KiB
Go
679 lines
21 KiB
Go
package services
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"pvfine/internal/pvf"
|
|
)
|
|
|
|
func TestDecodeDropRateGroupSupportsLegacySixValuesAndQuantizesToHundredths(t *testing.T) {
|
|
legacy := []int64{679750, 932250, 990000, 1000000, 1000001, 1000002}
|
|
extended := append(append([]int64(nil), legacy...), 123456)
|
|
|
|
for name, group := range map[string][]int64{"legacy": legacy, "extended": extended} {
|
|
t.Run(name, func(t *testing.T) {
|
|
rates, err := decodeDropRateGroup(group)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []int{6798, 2525, 577, 100, 0}
|
|
if got := rates; len(got) != len(want) {
|
|
t.Fatalf("rates = %#v, want %#v", got, want)
|
|
} else {
|
|
for index := range want {
|
|
if got[index] != want[index] {
|
|
t.Fatalf("rates = %#v, want %#v", got, want)
|
|
}
|
|
}
|
|
}
|
|
sum := 0
|
|
for _, rate := range rates {
|
|
sum += rate
|
|
}
|
|
if sum != dropRateTotal {
|
|
t.Fatalf("sum = %d, want %d", sum, dropRateTotal)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDecodeDropRateGroupSupportsLeadingZeroHellGroup(t *testing.T) {
|
|
// 真实 90US etc/itemdropinfo_monster_hell.etc 中的第 1 组与第 2 组 (困难 3% 史诗, 非常困难 5% 史诗)
|
|
group1 := []int64{0, 350000, 750000, 850000, 970000, 1000001, 1000001}
|
|
group2 := []int64{0, 350000, 750000, 850000, 950000, 1000002, 1000001}
|
|
|
|
rates1, err := decodeDropRateGroup(group1)
|
|
if err != nil {
|
|
t.Fatalf("group1 decode failed: %v", err)
|
|
}
|
|
want1 := []int{3500, 4000, 1000, 1200, 300}
|
|
for i := range want1 {
|
|
if rates1[i] != want1[i] {
|
|
t.Fatalf("rates1[%d] = %d, want %d", i, rates1[i], want1[i])
|
|
}
|
|
}
|
|
|
|
rates2, err := decodeDropRateGroup(group2)
|
|
if err != nil {
|
|
t.Fatalf("group2 decode failed: %v", err)
|
|
}
|
|
want2 := []int{3500, 4000, 1000, 1000, 500}
|
|
for i := range want2 {
|
|
if rates2[i] != want2[i] {
|
|
t.Fatalf("rates2[%d] = %d, want %d", i, rates2[i], want2[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecodeDropRateGroupRejectsDecreasingOrInvalidTerminal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
group []int64
|
|
}{
|
|
{name: "short", group: []int64{1, 2, 3}},
|
|
{name: "decreasing", group: []int64{100, 200, 150, 1000000, 1000001, 1000002}},
|
|
{name: "terminal too small", group: []int64{100, 200, 300, 400, 999999, 1000000}},
|
|
{name: "terminal too large", group: []int64{100, 200, 300, 400, 1000003, 1000000}},
|
|
{name: "leading zero decreasing", group: []int64{0, 300, 200, 400, 500, 1000001, 1000001}},
|
|
{name: "leading zero terminal too small", group: []int64{0, 100, 200, 300, 400, 999999, 1000001}},
|
|
{name: "leading zero terminal too large", group: []int64{0, 100, 200, 300, 400, 1000003, 1000001}},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if _, err := decodeDropRateGroup(test.group); err == nil {
|
|
t.Fatal("expected invalid group error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDropRateServiceReadsAndAppliesSevenValueGroups(t *testing.T) {
|
|
c := NewCore()
|
|
a := makeDropRateArchive(t)
|
|
if err := c.setArchive(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(c.closeArchive)
|
|
|
|
service := NewDropService(c)
|
|
document, err := service.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if document.PVFVersion != "90US" || len(document.Sections) != 4 {
|
|
t.Fatalf("document = %#v", document)
|
|
}
|
|
if got := document.Sections[0].Groups[0].Rates; len(got) != 5 {
|
|
t.Fatalf("hell rates = %#v", got)
|
|
}
|
|
if got := document.Sections[0].Groups[0].Rates[0]; got != 6000 {
|
|
t.Fatalf("hell first rate = %d, want 6000", got)
|
|
}
|
|
|
|
monster := document.Sections[2]
|
|
monster.Groups[0].Rates = []int{5000, 2500, 1500, 1000, 0}
|
|
result, err := service.Apply(DropRateApplyRequest{
|
|
Revision: document.Revision,
|
|
Sections: document.Sections,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(result.FileIndexes) != 1 || result.Revision == document.Revision {
|
|
t.Fatalf("apply result = %#v", result)
|
|
}
|
|
|
|
index, ok := a.Find("etc/itemdropinfo_monseter.etc")
|
|
if !ok {
|
|
t.Fatal("monster drop file missing")
|
|
}
|
|
raw, err := a.RawBytes(index)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parsed, err := a.ParseScriptDocument(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
section, ok := parsed.Section([]string{dropRateSectionName}, 0)
|
|
if !ok {
|
|
t.Fatal("drop section missing after apply")
|
|
}
|
|
values, err := scriptIntegerValues(section.Values(), "monster")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wantPrefix := []int64{500000, 750000, 900000, 1000000, 1000001, 1000002, 700}
|
|
for index, want := range wantPrefix {
|
|
if values[index] != want {
|
|
t.Fatalf("value[%d] = %d, want %d", index, values[index], want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDropRateApplyRejectsStaleOrInvalidRequestWithoutPartialMutation(t *testing.T) {
|
|
c := NewCore()
|
|
a := makeDropRateArchive(t)
|
|
if err := c.setArchive(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(c.closeArchive)
|
|
|
|
service := NewDropService(c)
|
|
document, err := service.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before := snapshotDropFile(t, a, "etc/itemdropinfo_monseter.etc")
|
|
|
|
document.Sections[2].Groups[0].Rates = []int{5000, 2500, 1500, 1000, 1}
|
|
if _, err := service.Apply(DropRateApplyRequest{Revision: document.Revision, Sections: document.Sections}); err == nil {
|
|
t.Fatal("expected invalid sum error")
|
|
}
|
|
if after := snapshotDropFile(t, a, "etc/itemdropinfo_monseter.etc"); string(after) != string(before) {
|
|
t.Fatal("invalid request changed the archive")
|
|
}
|
|
|
|
document, err = service.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
document.Sections[2].Groups[0].Rates = []int{5000, 2500, 1500, 1000, 0}
|
|
if _, err := service.Apply(DropRateApplyRequest{Revision: document.Revision, Sections: document.Sections}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Apply(DropRateApplyRequest{Revision: document.Revision, Sections: document.Sections}); !errors.Is(err, ErrDropRateStale) {
|
|
t.Fatalf("stale error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateDropRates(t *testing.T) {
|
|
if err := validateDropRates([]int{2000, 2000, 2000, 2000, 2000}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, rates := range [][]int{
|
|
{2000, 2000, 2000, 2000},
|
|
{2000, 2000, 2000, 2000, 2001},
|
|
{-1, 2000, 2000, 2000, 4000},
|
|
} {
|
|
if err := validateDropRates(rates); err == nil {
|
|
t.Fatalf("expected invalid rates: %#v", rates)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSupportedDropRateVersion(t *testing.T) {
|
|
for _, version := range []string{"90US", "90CN", "TW", "110US", "100CN", "115CN"} {
|
|
if !supportedDropRateVersion(version) {
|
|
t.Fatalf("version %s should be supported", version)
|
|
}
|
|
}
|
|
for _, version := range []string{"115US", "", "recovered"} {
|
|
if supportedDropRateVersion(version) {
|
|
t.Fatalf("version %s should be rejected", version)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDropRateServiceReadsRealSupportedArchive(t *testing.T) {
|
|
path := os.Getenv("PVF_TESTFILE")
|
|
if path == "" {
|
|
t.Skip("PVF_TESTFILE 未设置")
|
|
}
|
|
a, err := pvf.Open(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := NewCore()
|
|
if err := c.setArchive(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(c.closeArchive)
|
|
if !supportedDropRateVersion(a.ClientVersion()) {
|
|
t.Skipf("归档版本 %s 不属于掉率工具支持范围", a.ClientVersion())
|
|
}
|
|
|
|
document, err := NewDropService(c).Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(document.Sections) != len(dropRateFileSpecs) {
|
|
t.Fatalf("section count = %d, want %d", len(document.Sections), len(dropRateFileSpecs))
|
|
}
|
|
for index, section := range document.Sections {
|
|
if len(section.Groups) != dropRateFileSpecs[index].groupCount {
|
|
t.Fatalf("section %s groups = %d, want %d", section.Key, len(section.Groups), dropRateFileSpecs[index].groupCount)
|
|
}
|
|
for _, group := range section.Groups {
|
|
if err := validateDropRates(group.Rates); err != nil {
|
|
t.Fatalf("section %s rates %#v: %v", section.Key, group.Rates, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDropRateServiceReadsAndAppliesLeadingZeroHellArchive(t *testing.T) {
|
|
c := NewCore()
|
|
a := pvf.New()
|
|
files := map[string]string{
|
|
"etc/itemdropinfo_monster_hell.etc": "[basis of rarity dicision]\n2 0 350000 750000 850000 970000 1000001 1000001 0 350000 750000 850000 950000 1000002 1000001\n",
|
|
"etc/itemdropinfo_clearreward.etc": "[basis of rarity dicision]\n100000 300000 730000 930000 1000001 1000002 1000003\n",
|
|
"etc/itemdropinfo_monseter.etc": "[basis of rarity dicision]\n500000 800000 950000 980000 1000001 1000002 1000003 300000 500000 750000 950000 1000001 1000002 1000003 200000 300000 700000 920000 1000001 1000002 1000003 100000 300000 650000 880000 1000001 1000002 1000003\n",
|
|
"etc/itemdropinfo_monseter_extra.etc": "[basis of rarity dicision]\n500000 800000 950000 980000 1000001 1000002 1000003 300000 500000 750000 950000 1000001 1000002 1000003 200000 300000 700000 920000 1000001 1000002 1000003 100000 300000 650000 880000 1000001 1000002 1000003\n",
|
|
}
|
|
for path, text := range files {
|
|
if _, err := a.AddFileText(path, text, pvf.TypeScript); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := c.setArchive(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(c.closeArchive)
|
|
|
|
service := NewDropService(c)
|
|
document, err := service.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hell := document.Sections[0]
|
|
if hell.Key != "hell" || len(hell.Groups) != 2 {
|
|
t.Fatalf("hell section = %#v", hell)
|
|
}
|
|
if rates := hell.Groups[0].Rates; len(rates) != 5 || rates[4] != 300 {
|
|
t.Fatalf("hell group 1 rates = %#v, want epic 300 (3%%)", rates)
|
|
}
|
|
if rates := hell.Groups[1].Rates; len(rates) != 5 || rates[4] != 500 {
|
|
t.Fatalf("hell group 2 rates = %#v, want epic 500 (5%%)", rates)
|
|
}
|
|
|
|
hell.Groups[1].Rates = []int{2000, 2000, 500, 500, 5000}
|
|
result, err := service.Apply(DropRateApplyRequest{
|
|
Revision: document.Revision,
|
|
Sections: document.Sections,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(result.FileIndexes) != 1 {
|
|
t.Fatalf("modified file count = %d, want 1", len(result.FileIndexes))
|
|
}
|
|
|
|
reloaded, err := service.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []int{2000, 2000, 500, 500, 5000}
|
|
for i := range want {
|
|
if reloaded.Sections[0].Groups[1].Rates[i] != want[i] {
|
|
t.Fatalf("reloaded rates[%d] = %d, want %d", i, reloaded.Sections[0].Groups[1].Rates[i], want[i])
|
|
}
|
|
}
|
|
|
|
fileIndex, ok := a.Find("etc/itemdropinfo_monster_hell.etc")
|
|
if !ok {
|
|
t.Fatal("找不到深渊文件")
|
|
}
|
|
raw, err := a.RawBytes(fileIndex)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
doc, err := a.ParseScriptDocument(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sec, ok := doc.Section([]string{"basis of rarity dicision"}, 0)
|
|
if !ok {
|
|
t.Fatal("找不到 section")
|
|
}
|
|
values, err := scriptIntegerValues(sec.Values(), "etc/itemdropinfo_monster_hell.etc")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expectedTokens := []int64{2, 0, 350000, 750000, 850000, 970000, 1000001, 1000001, 0, 200000, 400000, 450000, 500000, 1000002, 1000001}
|
|
if len(values) != len(expectedTokens) {
|
|
t.Fatalf("values length = %d, want %d", len(values), len(expectedTokens))
|
|
}
|
|
for i, exp := range expectedTokens {
|
|
if values[i] != exp {
|
|
t.Fatalf("values[%d] = %d, want %d", i, values[i], exp)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecodeDropRateGroupWithZeroCommonRate(t *testing.T) {
|
|
// 普通怪物但白装率为 0% 的合法掉落配置 (终止阈值在 index 4)
|
|
group := []int64{0, 400000, 700000, 900000, 1000000, 700}
|
|
offset, err := detectDropRateGroupOffset(group)
|
|
if err != nil {
|
|
t.Fatalf("detectDropRateGroupOffset failed: %v", err)
|
|
}
|
|
if offset != 0 {
|
|
t.Fatalf("offset = %d, want 0", offset)
|
|
}
|
|
rates, err := decodeDropRateGroup(group)
|
|
if err != nil {
|
|
t.Fatalf("decode failed: %v", err)
|
|
}
|
|
want := []int{0, 4000, 3000, 2000, 1000}
|
|
for i := range want {
|
|
if rates[i] != want[i] {
|
|
t.Fatalf("rates[%d] = %d, want %d", i, rates[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecodeDropRateGroupWithZeroCommonRateAndExtendedSlots(t *testing.T) {
|
|
// 普通怪白装为 0%,且 index 5 是扩展终止位 1000001
|
|
group := []int64{0, 400000, 700000, 900000, 1000000, 1000001, 700}
|
|
offset, err := detectDropRateGroupOffset(group)
|
|
if err != nil {
|
|
t.Fatalf("detectDropRateGroupOffset failed: %v", err)
|
|
}
|
|
if offset != 0 {
|
|
t.Fatalf("offset = %d, want 0 (should not be misclassified as hell drop)", offset)
|
|
}
|
|
rates, err := decodeDropRateGroup(group)
|
|
if err != nil {
|
|
t.Fatalf("decode failed: %v", err)
|
|
}
|
|
want := []int{0, 4000, 3000, 2000, 1000}
|
|
for i := range want {
|
|
if rates[i] != want[i] {
|
|
t.Fatalf("rates[%d] = %d, want %d", i, rates[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDetectDropRateGroupOffsetRejectsInvalid(t *testing.T) {
|
|
// 长度不足
|
|
if _, err := detectDropRateGroupOffset([]int64{1, 2, 3}); err == nil {
|
|
t.Fatal("expected error for short group")
|
|
}
|
|
// index 4 和 index 5 均不是有效终止阈值
|
|
if _, err := detectDropRateGroupOffset([]int64{0, 200, 400, 600, 800, 900}); err == nil {
|
|
t.Fatal("expected error for invalid terminal thresholds")
|
|
}
|
|
}
|
|
|
|
func makeDropRateArchive(t *testing.T) *pvf.Archive {
|
|
t.Helper()
|
|
a := pvf.New()
|
|
files := map[string]string{
|
|
"etc/itemdropinfo_monster_hell.etc": "[basis of rarity dicision]\n2 600000 844900 950000 1000000 1000001 1000002 700 100000 400000 500000 800000 1000001 1000002 800\n",
|
|
"etc/itemdropinfo_clearreward.etc": "[basis of rarity dicision]\n679750 932250 990000 1000000 1000001 1000002 900\n",
|
|
"etc/itemdropinfo_monseter.etc": "[basis of rarity dicision]\n600000 844900 950000 1000000 1000001 1000002 700 330100 543690 999706 1000000 1000001 1000002 701 500000 944900 999500 1000000 1000001 1000002 702 700000 944900 995000 1000000 1000001 1000002 703\n",
|
|
"etc/itemdropinfo_monseter_extra.etc": "[basis of rarity dicision]\n600000 824900 950000 1000000 1000001 1000002 800 685000 992600 999500 1000000 1000001 1000002 801 500000 944900 999500 1000000 1000001 1000002 802 700000 944900 995000 1000000 1000001 1000002 803\n",
|
|
}
|
|
for path, text := range files {
|
|
if _, err := a.AddFileText(path, text, pvf.TypeScript); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
func snapshotDropFile(t *testing.T, a *pvf.Archive, path string) []byte {
|
|
t.Helper()
|
|
index, ok := a.Find(path)
|
|
if !ok {
|
|
t.Fatalf("file missing: %s", path)
|
|
}
|
|
raw, err := a.RawBytes(index)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return append([]byte(nil), raw...)
|
|
}
|
|
|
|
func TestDropRateServiceSixValueGroupsKeepExtraFields(t *testing.T) {
|
|
a := makeDropRateArchive(t)
|
|
for _, spec := range dropRateFileSpecs {
|
|
index, _ := a.Find(spec.path)
|
|
doc, err := a.ParseScriptDocument(snapshotDropFile(t, a, spec.path))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
section, _ := doc.Section([]string{dropRateSectionName}, 0)
|
|
values, err := scriptIntegerValues(section.Values(), spec.path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var text strings.Builder
|
|
text.WriteString("[" + dropRateSectionName + "]\n")
|
|
for i, value := range values {
|
|
if i >= spec.offset && (i-spec.offset)%7 == 6 {
|
|
continue
|
|
}
|
|
text.WriteString(strconv.FormatInt(value, 10))
|
|
text.WriteByte(' ')
|
|
}
|
|
if err := a.SetText(index, text.String()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
verifyDropRateApplyPreservesFields(t, a)
|
|
}
|
|
|
|
func verifyDropRateApplyPreservesFields(t *testing.T, a *pvf.Archive) {
|
|
t.Helper()
|
|
c := NewCore()
|
|
if err := c.setArchive(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(c.closeArchive)
|
|
service := NewDropService(c)
|
|
document, err := service.Read()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before := make(map[string][]int64)
|
|
beforeRaw := make(map[string][]byte)
|
|
for i, spec := range dropRateFileSpecs {
|
|
beforeRaw[spec.key] = snapshotDropFile(t, a, spec.path)
|
|
doc, err := a.ParseScriptDocument(snapshotDropFile(t, a, spec.path))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
section, _ := doc.Section([]string{dropRateSectionName}, 0)
|
|
before[spec.key], err = scriptIntegerValues(section.Values(), spec.path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
document.Sections[i].Groups[0].Rates = []int{1000, 2000, 3000, 4000, 0}
|
|
}
|
|
result, err := service.Apply(DropRateApplyRequest{Revision: document.Revision, Sections: document.Sections})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(result.FileIndexes) != 4 {
|
|
t.Fatalf("changed files = %d", len(result.FileIndexes))
|
|
}
|
|
for _, spec := range dropRateFileSpecs {
|
|
afterRaw := snapshotDropFile(t, a, spec.path)
|
|
original := beforeRaw[spec.key]
|
|
if len(afterRaw) != len(original) {
|
|
t.Fatalf("%s payload size changed", spec.key)
|
|
}
|
|
thresholdStart := -1
|
|
for pos := 0; pos+5 <= len(original); pos += 5 {
|
|
if original[pos] == 3 && a.ResolveString(int32(binary.LittleEndian.Uint32(original[pos+1:]))) == "["+dropRateSectionName+"]" {
|
|
thresholdStart = pos + 5 + spec.offset*5
|
|
break
|
|
}
|
|
}
|
|
if thresholdStart < 0 {
|
|
t.Fatal("drop section token missing")
|
|
}
|
|
for pos := 0; pos+5 <= len(original); pos += 5 {
|
|
if bytes.Equal(original[pos:pos+5], afterRaw[pos:pos+5]) {
|
|
continue
|
|
}
|
|
if pos < thresholdStart || pos >= thresholdStart+4*5 || original[pos] != 0 || afterRaw[pos] != 0 {
|
|
t.Fatalf("%s changed token outside the four probability thresholds at %d", spec.key, pos/5)
|
|
}
|
|
}
|
|
doc, err := a.ParseScriptDocument(afterRaw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
section, _ := doc.Section([]string{dropRateSectionName}, 0)
|
|
values, err := scriptIntegerValues(section.Values(), spec.path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, original := range before[spec.key] {
|
|
if i >= spec.offset && i < spec.offset+4 {
|
|
continue
|
|
}
|
|
if values[i] != original {
|
|
t.Fatalf("%s preserved value %d changed", spec.key, i)
|
|
}
|
|
}
|
|
}
|
|
var out bytes.Buffer
|
|
if err := a.SaveTo(&out); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := pvf.Parse(out.Bytes())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer b.Release()
|
|
if b.Format() != a.Format() {
|
|
t.Fatal("archive format changed")
|
|
}
|
|
for _, spec := range dropRateFileSpecs {
|
|
if !bytes.Equal(snapshotDropFile(t, a, spec.path), snapshotDropFile(t, b, spec.path)) {
|
|
t.Fatalf("%s payload changed on save/reopen", spec.key)
|
|
}
|
|
}
|
|
reopened, err := readDropRateDocument(b)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, section := range reopened.Sections {
|
|
want := []int{1000, 2000, 3000, 4000, 0}
|
|
if !equalDropRates(section.Groups[0].Rates, want) {
|
|
t.Fatalf("%s rates did not round trip", section.Key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDropRateServiceTWRealRoundTrip(t *testing.T) {
|
|
path := os.Getenv("PVF_TW_TESTFILE")
|
|
if path == "" {
|
|
t.Skip("PVF_TW_TESTFILE not set")
|
|
}
|
|
a, err := pvf.Open(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a.ClientVersion() != "TW" {
|
|
a.Release()
|
|
t.Fatal("fixture is not TW")
|
|
}
|
|
verifyDropRateApplyPreservesFields(t, a)
|
|
}
|
|
|
|
func TestDropRateGroupWidthRejectsInvalidLayout(t *testing.T) {
|
|
spec := dropRateFileSpecs[0]
|
|
for _, n := range []int{0, 12, 14, 16} {
|
|
if _, err := dropRateGroupWidth(make([]int64, n), spec); err == nil {
|
|
t.Fatalf("accepted %d values", n)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDropRateServiceEightAndNineValueGroups(t *testing.T) {
|
|
for _, width := range []int{8, 9} {
|
|
t.Run(strconv.Itoa(width), func(t *testing.T) {
|
|
a := makeDropRateArchive(t)
|
|
for _, spec := range dropRateFileSpecs {
|
|
index, _ := a.Find(spec.path)
|
|
doc, err := a.ParseScriptDocument(snapshotDropFile(t, a, spec.path))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
section, _ := doc.Section([]string{dropRateSectionName}, 0)
|
|
values, err := scriptIntegerValues(section.Values(), spec.path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var text strings.Builder
|
|
text.WriteString("[" + dropRateSectionName + "]\n")
|
|
if spec.offset > 0 {
|
|
text.WriteString(strconv.FormatInt(values[0], 10))
|
|
text.WriteByte(' ')
|
|
}
|
|
for group := 0; group < spec.groupCount; group++ {
|
|
start := spec.offset + group*7
|
|
for _, value := range values[start : start+6] {
|
|
text.WriteString(strconv.FormatInt(value, 10))
|
|
text.WriteByte(' ')
|
|
}
|
|
for j := 6; j < width; j++ {
|
|
text.WriteString(strconv.Itoa(1000000 + j - 3))
|
|
text.WriteByte(' ')
|
|
}
|
|
}
|
|
// Preserve another section as well as the extra threshold slots.
|
|
text.WriteString("\n[item drop rarity control]\n2 13 17 0 -5\n")
|
|
if err := a.SetText(index, text.String()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
verifyDropRateApplyPreservesFields(t, a)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDropRateServicePagedRealRoundTrip(t *testing.T) {
|
|
path := os.Getenv("PVF_TESTFILE")
|
|
if path == "" {
|
|
t.Skip("PVF_TESTFILE not set")
|
|
}
|
|
a, err := pvf.Open(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
version := a.ClientVersion()
|
|
if version != "110US" && version != "100CN" {
|
|
a.Release()
|
|
t.Skip("110US/100CN fixture required")
|
|
}
|
|
expectedWidth := 9
|
|
if version == "100CN" {
|
|
expectedWidth = 8
|
|
}
|
|
for _, spec := range dropRateFileSpecs {
|
|
doc, err := a.ParseScriptDocument(snapshotDropFile(t, a, spec.path))
|
|
if err != nil {
|
|
a.Release()
|
|
t.Fatal(err)
|
|
}
|
|
section, _ := doc.Section([]string{dropRateSectionName}, 0)
|
|
if section == nil {
|
|
a.Release()
|
|
t.Fatal("drop section missing")
|
|
}
|
|
values, err := scriptIntegerValues(section.Values(), spec.path)
|
|
if err != nil {
|
|
a.Release()
|
|
t.Fatal(err)
|
|
}
|
|
width, err := dropRateGroupWidth(values, spec)
|
|
if err != nil || width != expectedWidth {
|
|
a.Release()
|
|
t.Fatalf("unexpected %s group width: %d %v", version, width, err)
|
|
}
|
|
}
|
|
verifyDropRateApplyPreservesFields(t, a)
|
|
}
|