服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
214 lines
7.4 KiB
Go
214 lines
7.4 KiB
Go
package channelinfo
|
||
|
||
import (
|
||
"strconv"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func TestBuild90CNOnlineScriptExpandsPackedSourceServer(t *testing.T) {
|
||
data := []byte("[dungeon]\n`[sky_catle]` `sky` 11 12\n[/dungeon]\n[server]\n1 1 `tower` 2 `[none]` 0 0 0 6 `trade` 3 `[none]` 0 0 0 10 `forest` 0 `[granfloris]` 5 0 0 19 `crack` 1 `[crack]` 5 0 0 1 0 0 0\n[/server]\n[server]\n2 19 `other` 0 `[sky_catle]` 3 0 0\n[/server]\n")
|
||
|
||
script, err := Build90CNOnlineScript(data, 1, 0, 19)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
text := string(script)
|
||
for _, want := range []string{
|
||
"[dungeon]\n`[sky_catle]` `sky` 11 12\n[/dungeon]",
|
||
"[server]\n0\n",
|
||
"1 `tower` 11 `[none]` 0 0 0 ``\n",
|
||
"6 `trade` 3 `[none]` 0 0 0 ``\n",
|
||
"10 `forest` 22 `[granfloris]` 5 0 0 ``\n",
|
||
"19 `crack` 22 `[crack]` 5 0 0 1 0 0 0 ``\n",
|
||
} {
|
||
if !strings.Contains(text, want) {
|
||
t.Fatalf("online script is missing %q:\n%s", want, text)
|
||
}
|
||
}
|
||
if strings.Contains(text, "2 19 `other`") {
|
||
t.Fatalf("online script contains another server:\n%s", text)
|
||
}
|
||
|
||
index, err := Parse(script)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
channels := index.ChannelsForServer(0)
|
||
if len(channels) != 4 {
|
||
t.Fatalf("online channels = %d, want 4: %+v", len(channels), channels)
|
||
}
|
||
if channels[0].Type != 11 || channels[1].Type != 3 || channels[2].Type != 22 || channels[3].Type != 22 {
|
||
t.Fatalf("online channel types = %d/%d/%d/%d", channels[0].Type, channels[1].Type, channels[2].Type, channels[3].Type)
|
||
}
|
||
}
|
||
|
||
func TestBuild90CNOnlineScriptAcceptsInlineServerID(t *testing.T) {
|
||
data := []byte("[dungeon]\n`[granfloris]`\n<4::channel_info_dname_1>\n3\n[/dungeon]\n[server] 1\n10 `<chn_channel_info_028>` 11 `[granfloris]` 5 0.0 0.0 ``\n19 `<chn_channel_info_028>` 11 `[granfloris]` 5 0 0 ``\n[/server]\n")
|
||
|
||
script, err := Build90CNOnlineScript(data, 1, 0, 19)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
index, err := Parse(script)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
channels := index.ChannelsForServer(0)
|
||
if len(channels) != 2 || channels[0].ID != 10 || channels[1].ID != 19 {
|
||
t.Fatalf("online inline channels mismatch: %+v", channels)
|
||
}
|
||
if channels[0].NameKey != "chn_channel_info_028" ||
|
||
channels[1].NameKey != "chn_channel_info_028" ||
|
||
channels[0].AreaKey != "granfloris" ||
|
||
channels[1].AreaKey != "granfloris" {
|
||
t.Fatalf("online inline source mapping was not preserved: %+v", channels)
|
||
}
|
||
}
|
||
|
||
func TestBuild90CNOnlineScriptPreservesSourceMappingForChannel38(t *testing.T) {
|
||
data := []byte("[server]\n1\n19 `crack` 1 `[crack]` 5 0 0\n38 `trade` 3 `[trade]` 0 0 0\n[/server]\n")
|
||
|
||
script, err := Build90CNOnlineScript(data, 1, 0, 19)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
text := string(script)
|
||
if !strings.Contains(text, "38 `trade` 3 `[trade]`") {
|
||
t.Fatalf("online script did not preserve channel 38 source mapping:\n%s", text)
|
||
}
|
||
}
|
||
|
||
func TestBuild90CNOnlineScriptPreservesOriginPvpChannels(t *testing.T) {
|
||
data := []byte("[dungeon]\n`[pvp]`\n<4::chn_channel_info_044>\n[/dungeon]\n[server] 1\n" +
|
||
"19 `<chn_channel_info_028>` 11 `[granfloris]` 5 0 0 ``\n" +
|
||
"501 `<chn_channel_info_039>` 24 `[pvp]` 0 0 0 ``\n" +
|
||
"502 `<chn_channel_info_040>` 24 `[pvp]` 0 0 0 ``\n" +
|
||
"503 `<chn_channel_info_041>` 24 `[pvp]` 0 0 0 ``\n" +
|
||
"504 `<chn_channel_info_042>` 24 `[pvp]` 0 0 0 ``\n" +
|
||
"505 `<chn_channel_info_043>` 24 `[pvp]` 0 0 0 ``\n" +
|
||
"506 `<chn_channel_info_044>` 24 `[pvp]` 0 0 0 ``\n" +
|
||
"507 `<chn_channel_info_045>` 24 `[pvp]` 0 0 0 ``\n" +
|
||
"[/server]\n")
|
||
|
||
script, err := Build90CNOnlineScript(data, 1, 0, 19)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
index, err := Parse(script)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
channels := index.ChannelsForServer(0)
|
||
if len(channels) != 8 {
|
||
t.Fatalf("online channels = %d, want 8: %+v", len(channels), channels)
|
||
}
|
||
for i, channel := range channels[1:] {
|
||
wantID := 501 + i
|
||
wantNameKey := "chn_channel_info_0" + strconv.Itoa(39+i)
|
||
if channel.ID != wantID || channel.Type != 24 ||
|
||
channel.AreaKey != "pvp" || channel.NameKey != wantNameKey {
|
||
t.Fatalf("pvp channel %d mismatch: %+v", wantID, channel)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestBuild90CNOnlineScriptNormalizesExistingRecordTerminators(t *testing.T) {
|
||
data := []byte("[server]\r\n1\r\n10 `forest` 1 `[granfloris]` 5 0 0 ``\r\n19 `crack` 1 `[crack]` 5 0 0 ``\r\n[/server]\r\n")
|
||
|
||
script, err := Build90CNOnlineScript(data, 1, 0, 19)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
text := string(script)
|
||
if strings.Contains(text, " `` ``\n") {
|
||
t.Fatalf("online script contains duplicate record terminators:\n%s", text)
|
||
}
|
||
if got := strings.Count(text, " ``\n"); got != 2 {
|
||
t.Fatalf("online script terminators = %d, want 2:\n%s", got, text)
|
||
}
|
||
index, err := Parse(script)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := len(index.ChannelsForServer(0)); got != 2 {
|
||
t.Fatalf("online channels = %d, want 2", got)
|
||
}
|
||
}
|
||
|
||
func TestOnlineTypeFor90CNSeparatesDirectoryTypeFromRawType(t *testing.T) {
|
||
tests := []struct {
|
||
channelID int
|
||
channelType int
|
||
want int
|
||
}{
|
||
{channelID: 10, channelType: 0, want: 22},
|
||
{channelID: 31, channelType: 1, want: 22},
|
||
{channelID: 1, channelType: 2, want: 11},
|
||
{channelID: 6, channelType: 3, want: 3},
|
||
{channelID: 51, channelType: 4, want: 4},
|
||
}
|
||
for _, test := range tests {
|
||
if got := OnlineTypeFor90CN(test.channelID, test.channelType); got != test.want {
|
||
t.Errorf("OnlineTypeFor90CN(%d, %d) = %d, want %d", test.channelID, test.channelType, got, test.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestBuild90CNOnlineScriptRequiresBootstrapChannel(t *testing.T) {
|
||
_, err := Build90CNOnlineScript([]byte("[server]\n1 10 `forest` 1 `[granfloris]` 0 0\n[/server]\n"), 1, 1, 19)
|
||
if err == nil || !strings.Contains(err.Error(), "missing bootstrap channel 19") {
|
||
t.Fatalf("error = %v, want missing bootstrap channel", err)
|
||
}
|
||
}
|
||
|
||
// TestBuild90CNOnlineScriptAllowingMissingBootstrap 宽松版必须照常产出脚本,
|
||
// 并用第二个返回值把"源表没有引导频道"这件事告诉调用方(由它决定告警还是拒绝)。
|
||
func TestBuild90CNOnlineScriptAllowingMissingBootstrap(t *testing.T) {
|
||
script, bootstrapPresent, err := Build90CNOnlineScriptAllowingMissingBootstrap(
|
||
[]byte("[server]\n1 171 `chn_channel_info_014` 22 `[deathtower]` 0 0\n[/server]\n"),
|
||
1,
|
||
1,
|
||
19,
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("宽松模式下不该失败: %v", err)
|
||
}
|
||
if bootstrapPresent {
|
||
t.Fatal("源表里没有 19,bootstrapPresent 必须是 false")
|
||
}
|
||
online, err := Parse(script)
|
||
if err != nil {
|
||
t.Fatalf("宽松模式产出的脚本必须仍可解析: %v", err)
|
||
}
|
||
if _, ok := online.ServerChannel(1, 171); !ok {
|
||
t.Fatalf("宽松模式脚本里没有 171:\n%s", script)
|
||
}
|
||
|
||
// 引导频道在表里时必须报告 true,且严格版与宽松版产出一致。
|
||
withBootstrap := []byte("[server]\n1 19 `chn_channel_info_028` 11 `[granfloris]` 0 0\n[/server]\n")
|
||
relaxedScript, present, err := Build90CNOnlineScriptAllowingMissingBootstrap(withBootstrap, 1, 1, 19)
|
||
if err != nil || !present {
|
||
t.Fatalf("含引导频道时 present=%v err=%v, want true/nil", present, err)
|
||
}
|
||
strictScript, err := Build90CNOnlineScript(withBootstrap, 1, 1, 19)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if string(relaxedScript) != string(strictScript) {
|
||
t.Fatalf("宽松版与严格版产出不一致:\n宽松:\n%s\n严格:\n%s", relaxedScript, strictScript)
|
||
}
|
||
}
|
||
|
||
func TestBuild90CNOnlineScriptRejectsUnquotedChannelFields(t *testing.T) {
|
||
_, err := Build90CNOnlineScript(
|
||
[]byte("[server]\n1\n19 crack 1 [crack] 5 0 0\n[/server]\n"),
|
||
1,
|
||
0,
|
||
19,
|
||
)
|
||
if err == nil || !strings.Contains(err.Error(), "invalid packed channel row") {
|
||
t.Fatalf("error = %v, want invalid packed channel row", err)
|
||
}
|
||
}
|