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,529 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
func newMagicBoxTestCatalog(t *testing.T) (*pvfDungeonDropCatalog, dungeonDropCatalogTestSource) {
|
||||
t.Helper()
|
||||
source := dungeonDropCatalogTestSource{
|
||||
"monster/monster.lst": "",
|
||||
"equipment/equipment.lst": "101600024 `avatar/weapon.equ`\n",
|
||||
"stackable/stackable.lst": "24 `cash/creature/creature_food.stk`\n10007368 `ect/chn_random/amazingbox.stk`\n10007367 `ect/chn_random/hammer.stk`\n2001 `booster/boosterbox.stk`\n2002 `other/plain.stk`\n2003 `booster/legacy_avatar_box.stk`\n2004 `booster/upgradable_legacy_box.stk`\n2600014 `potion/ptn.stk`\n2682272 `other/reward2682272.stk`\n3000 `other/reward3000.stk`\n3001 `other/reward3001.stk`\n3002 `other/reward3002.stk`\n",
|
||||
"stackable/ect/chn_random/amazingbox.stk": "[name] `Amazing Box`\n[stackable type] `[random upgradable legacy]`\n[RANDOMBOX]\n[int data]\n2600014 2 0 2600014 18000 2 0 2600014 5000 2 0 2600014 500 2 0 2600014 2 2 0\n[/int data]\n[int data]\n2682272 1 0 2682272 50000 1 0\n[/int data]\n[sealing removal item]\n2 10007367 1 10007368 1\n[/sealing removal item]\n[/RANDOMBOX]\n",
|
||||
"stackable/ect/chn_random/hammer.stk": "[name] `Hammer`\n[stackable type] `[material]`\n",
|
||||
"stackable/booster/boosterbox.stk": "[name] `Booster Box`\n[stackable type] `[booster]`\n[booster info]\n[etc]\n2 3001 100 1 3002 50 2\n[/etc]\n[/booster info]\n[need material] `10007367 1`\n",
|
||||
"stackable/booster/legacy_avatar_box.stk": "[name] `Legacy Avatar Box`\n[stackable type] `[booster]`\n[booster info]\n[avatar]\n1 101600024 100000 1 3 2\n[/avatar]\n[/booster info]\n",
|
||||
"stackable/booster/upgradable_legacy_box.stk": "[name] `Old Event Box`\n[stackable type] `[upgradable legacy]`\n[int data]\n3000 0 1 3001 217 1 3002 1430 2\n[/int data]\n[need material]\n10007367 1\n[/need material]\n",
|
||||
"stackable/other/plain.stk": "[name] `Plain`\n[stackable type] `[material]`\n",
|
||||
"stackable/other/reward2682272.stk": "[name] `Reward 2682272`\n[stackable type] `[material]`\n",
|
||||
"stackable/other/reward3000.stk": "[name] `Reward 3000`\n[stackable type] `[material]`\n",
|
||||
"stackable/other/reward3001.stk": "[name] `Reward 3001`\n[stackable type] `[material]`\n",
|
||||
"stackable/other/reward3002.stk": "[name] `Reward 3002`\n[stackable type] `[material]`\n",
|
||||
"stackable/potion/ptn.stk": "[name] `Potion`\n[stackable type] `[consumption]`\n[stack limit] 100\n[usable period] 30\n[attach type] `[sealing]`\n",
|
||||
"stackable/cash/creature/creature_food.stk": "[name] `Creature Food`\n[stackable type] `[feed]`\n[stack limit] 1000\n",
|
||||
"equipment/avatar/weapon.equ": "[name] `Legacy Weapon Avatar`\n[equipment type] `[weapon avatar]` 0\n[attach type] `[trade]`\n",
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return catalog, source
|
||||
}
|
||||
|
||||
// [booster info] 里的 `[charactor]` 是按职业分组的(首 token 是职业标签,
|
||||
// 后面是 minLevel/maxLevel/drawCount,再接普通的 (item, weight, count) 三元组)。
|
||||
// 解析器必须把职业门读出来,否则开箱时无从过滤 —— 现场就是鬼剑士开豪华婚礼
|
||||
// 化妆箱把全职业的时装一起领走(2026-10-04)。
|
||||
func TestParseMagicBoxBoosterCharactorGroupsCarryJobGate(t *testing.T) {
|
||||
// 直接解析一段 PVF 文本,避免改动共享 fixture 影响其它用例。
|
||||
doc, err := dnfpvf.Parse("box.stk", "[booster info]\n"+
|
||||
"[charactor]\n`[swordman]` 1 99 1 3001 100000 1\n[/charactor]\n"+
|
||||
"[charactor]\n`[at swordman]` 30 99 2 3002 100000 1\n[/charactor]\n"+
|
||||
"[etc]\n1 3000 100000 2\n[/etc]\n"+
|
||||
"[/booster info]")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
groups := parseMagicBoxBoosterGroups(doc)
|
||||
if len(groups) != 3 {
|
||||
t.Fatalf("分组数=%d,应为 3: %+v", len(groups), groups)
|
||||
}
|
||||
if groups[0].Job != "[swordman]" || groups[0].MinLevel != 1 || groups[0].MaxLevel != 99 || groups[0].DrawCount != 1 {
|
||||
t.Fatalf("第一组职业门=%+v", groups[0])
|
||||
}
|
||||
if groups[1].Job != "[at swordman]" || groups[1].MinLevel != 30 || groups[1].DrawCount != 2 {
|
||||
t.Fatalf("第二组职业门=%+v", groups[1])
|
||||
}
|
||||
// 普通 [etc] 组不带职业门。
|
||||
if groups[2].Job != "" || groups[2].MinLevel != 0 || groups[2].MaxLevel != 0 {
|
||||
t.Fatalf("etc 组不应带职业门: %+v", groups[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxHistoricalAvatarFiveFieldRow(t *testing.T) {
|
||||
catalog, source := newMagicBoxTestCatalog(t)
|
||||
definition, err := resolveCurrentBoosterDefinition(catalog, 2003, currentBoosterRequestRandom)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve historical avatar box: %v", err)
|
||||
}
|
||||
amounts, options, gold, err := resolveCurrentBoosterRewardAmounts(
|
||||
definition,
|
||||
currentBoosterOpenRequest{Kind: currentBoosterRequestRandom},
|
||||
func(limit int64) (int64, error) { return 0, nil },
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("roll historical avatar box: %v", err)
|
||||
}
|
||||
if len(amounts) != 1 || amounts[101600024] != 1 || amounts[1] != 0 || options[101600024] != 2 || gold != 0 {
|
||||
t.Fatalf("historical avatar amounts=%v options=%v gold=%d", amounts, options, gold)
|
||||
}
|
||||
rewards, err := resolveCurrentBoosterRewards(
|
||||
catalog,
|
||||
amounts,
|
||||
options,
|
||||
dnfrepo.ItemStack{ItemID: 2003, Count: 1},
|
||||
time.Now().UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve historical avatar reward: %v", err)
|
||||
}
|
||||
if len(rewards) != 1 || rewards[0].Definition.ItemID != 101600024 || rewards[0].Count != 1 || rewards[0].Option != 2 || rewards[0].Placement != currentBoosterRewardAvatar {
|
||||
t.Fatalf("historical avatar rewards=%+v", rewards)
|
||||
}
|
||||
inventory := dnfrepo.InventoryRecord{Slots: make(map[string]dnfrepo.ItemStack)}
|
||||
// 24 = 最大扩展,使落点范围不被夹取,等价于本用例原本的落点语义。
|
||||
keys, err := grantCurrentBoosterReward(&inventory, source, rewards[0], 24)
|
||||
if err != nil {
|
||||
t.Fatalf("grant historical avatar reward: %v", err)
|
||||
}
|
||||
if len(keys) != 1 || keys[0] != "1:0" {
|
||||
t.Fatalf("historical avatar keys=%v", keys)
|
||||
}
|
||||
avatar := inventory.Slots["1:0"]
|
||||
if avatar.ItemID != 101600024 || avatar.Count != 1 {
|
||||
t.Fatalf("historical avatar stack=%+v", avatar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealPVFHistoricalAvatarBoosterUsesFiveFieldRow(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify the historical avatar booster")
|
||||
}
|
||||
archive, err := platformpvf.LoadArchive(platformpvf.Options{Path: pvfPath})
|
||||
if err != nil {
|
||||
t.Fatalf("load real Script.pvf: %v", err)
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(archive)
|
||||
if err != nil {
|
||||
t.Fatalf("load real item catalog: %v", err)
|
||||
}
|
||||
definition, err := resolveCurrentBoosterDefinition(catalog, 10007317, currentBoosterRequestRandom)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve historical avatar booster 10007317: %v", err)
|
||||
}
|
||||
if len(definition.Random.Groups) != 1 || definition.Random.Groups[0].DrawCount != 1 || len(definition.Random.Groups[0].Entries) != 1 {
|
||||
t.Fatalf("historical avatar booster groups=%+v", definition.Random.Groups)
|
||||
}
|
||||
entry := definition.Random.Groups[0].Entries[0]
|
||||
if entry.ItemID != 101600024 || entry.Weight != 100000 || entry.Count != 1 || entry.Option != 0 {
|
||||
t.Fatalf("historical avatar booster entry=%+v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealPVFUpgradableLegacyGiftBoxUsesNativeMagicBoxPool(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify the historical upgradable-legacy gift box")
|
||||
}
|
||||
archive, err := platformpvf.LoadArchive(platformpvf.Options{Path: pvfPath})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolution, err := resolveCurrentMagicBox(catalog, archive, 10002952)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resolution.Kind != "random" || len(resolution.Groups) != 1 || len(resolution.Groups[0].Entries) < 100 {
|
||||
t.Fatalf("rainbow gift box resolution=%+v", resolution)
|
||||
}
|
||||
if resolution.MaterialItemID != 10002941 || resolution.MaterialCountPerUse != 1 {
|
||||
t.Fatalf("rainbow gift box material=%d x%d", resolution.MaterialItemID, resolution.MaterialCountPerUse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealPVFAllNativeLotteryJarPoolsResolve(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to audit every native lottery jar")
|
||||
}
|
||||
archive, err := platformpvf.LoadArchive(platformpvf.Options{Path: pvfPath})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jarCount := 0
|
||||
rewardCount := 0
|
||||
for _, itemID := range catalog.stackableIDs {
|
||||
source, resolveErr := catalog.ResolveItem(itemID)
|
||||
if resolveErr != nil {
|
||||
t.Fatalf("resolve source item=%d: %v", itemID, resolveErr)
|
||||
}
|
||||
if !isCurrentLotteryJarType(normalizeDungeonDropStackableType(source.StackableType)) {
|
||||
continue
|
||||
}
|
||||
jarCount++
|
||||
definition, resolveErr := resolveCurrentBoosterDefinition(catalog, itemID, currentBoosterRequestLottery)
|
||||
if resolveErr != nil {
|
||||
t.Fatalf("resolve lottery jar item=%d name=%q path=%s: %v", itemID, source.Name, source.PVFPath, resolveErr)
|
||||
}
|
||||
document, documentErr := parseDungeonCardPVFDocument(catalog.source, source.PVFPath)
|
||||
if documentErr != nil {
|
||||
t.Fatalf("read lottery jar item=%d path=%s: %v", itemID, source.PVFPath, documentErr)
|
||||
}
|
||||
openingItemID, openingCount := resolveMagicBoxSealingRemovalMaterial(document, int64(itemID))
|
||||
if definition.MaterialItemID != 0 || definition.MaterialCount != 0 ||
|
||||
definition.Random.MaterialItemID != openingItemID || definition.Random.MaterialCountPerUse != openingCount {
|
||||
t.Fatalf(
|
||||
"lottery jar item=%d name=%q mixed shop/opening material: definition=%d x%d random=%d x%d want sealing=%d x%d",
|
||||
itemID,
|
||||
source.Name,
|
||||
definition.MaterialItemID,
|
||||
definition.MaterialCount,
|
||||
definition.Random.MaterialItemID,
|
||||
definition.Random.MaterialCountPerUse,
|
||||
openingItemID,
|
||||
openingCount,
|
||||
)
|
||||
}
|
||||
for groupIndex, group := range definition.Random.Groups {
|
||||
if len(group.Entries) == 0 {
|
||||
t.Fatalf("lottery jar item=%d group=%d is empty", itemID, groupIndex)
|
||||
}
|
||||
for _, entry := range group.Entries {
|
||||
if entry.Gold {
|
||||
if entry.ItemID != 0 || entry.Count <= 0 {
|
||||
t.Fatalf("lottery jar item=%d has invalid gold reward=%+v", itemID, entry)
|
||||
}
|
||||
rewardCount++
|
||||
continue
|
||||
}
|
||||
if entry.ItemID <= 0 || entry.ItemID > int64(^uint32(0)) {
|
||||
t.Fatalf("lottery jar item=%d has invalid reward=%d", itemID, entry.ItemID)
|
||||
}
|
||||
if _, rewardErr := catalog.ResolveItem(uint32(entry.ItemID)); rewardErr != nil {
|
||||
t.Fatalf("lottery jar item=%d reward=%d is not mapped: %v", itemID, entry.ItemID, rewardErr)
|
||||
}
|
||||
rewardCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
if jarCount == 0 || rewardCount == 0 {
|
||||
t.Fatalf("native lottery jar audit jars=%d rewards=%d", jarCount, rewardCount)
|
||||
}
|
||||
t.Logf("native lottery jar audit jars=%d mapped_reward_rows=%d", jarCount, rewardCount)
|
||||
}
|
||||
|
||||
func TestRealPVFAllNamedPocketJarRoutesResolve(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to audit every named pocket jar route")
|
||||
}
|
||||
archive, err := platformpvf.LoadArchive(platformpvf.Options{Path: pvfPath})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
typeCounts := make(map[string]int)
|
||||
unsupported := make([]string, 0)
|
||||
routeErrors := make([]string, 0)
|
||||
jarCount := 0
|
||||
for _, itemID := range catalog.stackableIDs {
|
||||
source, resolveErr := catalog.ResolveItem(itemID)
|
||||
if resolveErr != nil {
|
||||
t.Fatalf("resolve source item=%d: %v", itemID, resolveErr)
|
||||
}
|
||||
if !strings.Contains(source.Name, "袖珍罐") {
|
||||
continue
|
||||
}
|
||||
jarCount++
|
||||
stackableType := normalizeDungeonDropStackableType(source.StackableType)
|
||||
typeCounts[stackableType]++
|
||||
switch stackableType {
|
||||
case "[upgradable legacy]", "[random upgradable legacy]":
|
||||
_, resolveErr = resolveCurrentBoosterDefinition(catalog, itemID, currentBoosterRequestLottery)
|
||||
case "[booster]", "[cera booster]", "[booster random]":
|
||||
_, resolveErr = resolveCurrentBoosterDefinition(catalog, itemID, currentBoosterRequestRandom)
|
||||
case "[booster selection]":
|
||||
_, resolveErr = resolveCurrentBoosterDefinition(catalog, itemID, currentBoosterRequestSelection)
|
||||
default:
|
||||
unsupported = append(unsupported, fmt.Sprintf("%d %q type=%q path=%s", itemID, source.Name, stackableType, source.PVFPath))
|
||||
continue
|
||||
}
|
||||
if resolveErr != nil {
|
||||
routeErrors = append(routeErrors, fmt.Sprintf("%d %q type=%q path=%s: %v", itemID, source.Name, stackableType, source.PVFPath, resolveErr))
|
||||
}
|
||||
}
|
||||
sort.Strings(unsupported)
|
||||
sort.Strings(routeErrors)
|
||||
if len(routeErrors) != 0 {
|
||||
t.Fatalf("named pocket jars with a broken current route (%d):\n%s", len(routeErrors), strings.Join(routeErrors, "\n"))
|
||||
}
|
||||
if len(unsupported) != 0 {
|
||||
// Name matching also finds fragments, keys, services and twelve
|
||||
// retired 2010-2014 [etc]/[legacy] definitions. They have no current
|
||||
// opener type and must not be promoted into op27 based on their name.
|
||||
t.Logf("named pocket-jar terms without a current opener (%d):\n%s", len(unsupported), strings.Join(unsupported, "\n"))
|
||||
}
|
||||
if jarCount == 0 {
|
||||
t.Fatal("named pocket jar audit found no items")
|
||||
}
|
||||
t.Logf("named pocket jar audit jars=%d routes=%v", jarCount, typeCounts)
|
||||
}
|
||||
|
||||
func TestResolveCurrentBoosterLotteryIgnoresLegacyShopNeedMaterial(t *testing.T) {
|
||||
catalog, _ := newMagicBoxTestCatalog(t)
|
||||
|
||||
lottery, err := resolveCurrentBoosterDefinition(catalog, 2004, currentBoosterRequestLottery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if lottery.MaterialItemID != 0 || lottery.MaterialCount != 0 ||
|
||||
lottery.Random.MaterialItemID != 0 || lottery.Random.MaterialCountPerUse != 0 {
|
||||
t.Fatalf("lottery shop material leaked into opening cost: %+v", lottery)
|
||||
}
|
||||
|
||||
magicBox, err := resolveCurrentMagicBox(catalog, catalog.source, 2004)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if magicBox.MaterialItemID != 10007367 || magicBox.MaterialCountPerUse != 1 {
|
||||
t.Fatalf("non-lottery magic-box material=%d x%d", magicBox.MaterialItemID, magicBox.MaterialCountPerUse)
|
||||
}
|
||||
|
||||
randomLegacy, err := resolveCurrentBoosterDefinition(catalog, 10007368, currentBoosterRequestLottery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if randomLegacy.MaterialItemID != 0 || randomLegacy.MaterialCount != 0 ||
|
||||
randomLegacy.Random.MaterialItemID != 10007367 || randomLegacy.Random.MaterialCountPerUse != 1 {
|
||||
t.Fatalf("lottery opening key was not preserved: %+v", randomLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentBoosterRandomIgnoresShopNeedMaterial(t *testing.T) {
|
||||
catalog, _ := newMagicBoxTestCatalog(t)
|
||||
|
||||
booster, err := resolveCurrentBoosterDefinition(catalog, 2001, currentBoosterRequestRandom)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if booster.MaterialItemID != 0 || booster.MaterialCount != 0 ||
|
||||
booster.Random.MaterialItemID != 0 || booster.Random.MaterialCountPerUse != 0 {
|
||||
t.Fatalf("booster shop material leaked into opening cost: %+v", booster)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxPrunesUnlistedLegacyRewardRows(t *testing.T) {
|
||||
source := dungeonDropCatalogTestSource{
|
||||
"monster/monster.lst": "",
|
||||
"equipment/equipment.lst": "",
|
||||
"stackable/stackable.lst": "7000 `legacy/jar.stk`\n7001 `legacy/reward.stk`\n",
|
||||
"stackable/legacy/jar.stk": "[name] `Legacy Jar`\n[stackable type] `[upgradable legacy]`\n[int data]\n7001 10 1 7999 1000 1\n[/int data]\n",
|
||||
"stackable/legacy/reward.stk": "[name] `Mapped Reward`\n[stackable type] `[material]`\n",
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolution, err := resolveCurrentMagicBox(catalog, source, 7000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resolution.Groups) != 1 || len(resolution.Groups[0].Entries) != 1 || resolution.Groups[0].Entries[0].ItemID != 7001 {
|
||||
t.Fatalf("filtered legacy jar=%+v", resolution.Groups)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxUpgradableLegacyGoldPseudoReward(t *testing.T) {
|
||||
source := dungeonDropCatalogTestSource{
|
||||
"monster/monster.lst": "",
|
||||
"equipment/equipment.lst": "",
|
||||
"stackable/stackable.lst": "7614 `event/gold_jar.stk`\n",
|
||||
"stackable/event/gold_jar.stk": "[name] `Gold Jar`\n[stackable type] `[upgradable legacy]`\n[int data]\n0 1 1 0 2 10000\n[/int data]\n",
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
definition, err := resolveCurrentBoosterDefinition(catalog, 7614, currentBoosterRequestLottery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(definition.Random.Groups) != 1 || len(definition.Random.Groups[0].Entries) != 2 || !definition.Random.Groups[0].Entries[0].Gold {
|
||||
t.Fatalf("gold jar groups=%+v", definition.Random.Groups)
|
||||
}
|
||||
amounts, options, gold, err := resolveCurrentBoosterRewardAmounts(
|
||||
definition,
|
||||
currentBoosterOpenRequest{Kind: currentBoosterRequestLottery},
|
||||
func(limit int64) (int64, error) {
|
||||
if limit != 3 {
|
||||
t.Fatalf("roll limit=%d", limit)
|
||||
}
|
||||
return 2, nil
|
||||
},
|
||||
)
|
||||
if err != nil || len(amounts) != 0 || len(options) != 0 || gold != 10000 {
|
||||
t.Fatalf("gold jar amounts=%v options=%v gold=%d err=%v", amounts, options, gold, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxPetFeedTargetsPetConsumableSegment(t *testing.T) {
|
||||
catalog, source := newMagicBoxTestCatalog(t)
|
||||
item, err := resolveCurrentMagicBoxRewardItem(catalog, source, 24)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if item.ItemID != 24 || item.TargetListType != currentPetInventoryListType ||
|
||||
item.SlotStart != currentCeraShopPetConsumableSlotStart ||
|
||||
item.SlotEnd != currentCeraShopPetConsumableSlotEnd ||
|
||||
item.StackLimit != 1000 {
|
||||
t.Fatalf("pet feed reward=%+v", item)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxRandomLegacy(t *testing.T) {
|
||||
catalog, source := newMagicBoxTestCatalog(t)
|
||||
resolution, err := resolveCurrentMagicBox(catalog, source, 10007368)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve error = %v", err)
|
||||
}
|
||||
if resolution.Kind != "random" {
|
||||
t.Fatalf("kind = %q, want random", resolution.Kind)
|
||||
}
|
||||
if len(resolution.Groups) != 2 {
|
||||
t.Fatalf("groups = %d, want 2", len(resolution.Groups))
|
||||
}
|
||||
first := resolution.Groups[0]
|
||||
if first.DrawCount != 1 || len(first.Entries) != 4 {
|
||||
t.Fatalf("group[0] = %+v, want 4 entries DrawCount 1", first)
|
||||
}
|
||||
if first.Entries[0].ItemID != 2600014 || first.Entries[0].Weight != 18000 || first.Entries[0].Count != 2 {
|
||||
t.Fatalf("group[0] entry[0] = %+v", first.Entries[0])
|
||||
}
|
||||
if first.Entries[3].Weight != 2 {
|
||||
t.Fatalf("group[0] entry[3] = %+v, want weight 2 preserved", first.Entries[3])
|
||||
}
|
||||
second := resolution.Groups[1]
|
||||
// Seven ints: exactly one quad entry after the skipped leading triple.
|
||||
if len(second.Entries) != 1 || second.Entries[0].ItemID != 2682272 || second.Entries[0].Weight != 50000 || second.Entries[0].Count != 1 {
|
||||
t.Fatalf("group[1] = %+v", second)
|
||||
}
|
||||
if resolution.MaterialItemID != 10007367 || resolution.MaterialCountPerUse != 1 {
|
||||
t.Fatalf("material = %d x%d, want 10007367 x1", resolution.MaterialItemID, resolution.MaterialCountPerUse)
|
||||
}
|
||||
if resolution.BoxPVFPath != "stackable/ect/chn_random/amazingbox.stk" {
|
||||
t.Fatalf("path = %q", resolution.BoxPVFPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxUpgradableLegacyTopLevelTriples(t *testing.T) {
|
||||
catalog, source := newMagicBoxTestCatalog(t)
|
||||
resolution, err := resolveCurrentMagicBox(catalog, source, 2004)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resolution.Kind != "random" || len(resolution.Groups) != 1 || resolution.Groups[0].DrawCount != 1 {
|
||||
t.Fatalf("resolution=%+v", resolution)
|
||||
}
|
||||
entries := resolution.Groups[0].Entries
|
||||
if len(entries) != 2 || entries[0].ItemID != 3001 || entries[0].Weight != 217 || entries[0].Count != 1 ||
|
||||
entries[1].ItemID != 3002 || entries[1].Weight != 1430 || entries[1].Count != 2 {
|
||||
t.Fatalf("entries=%+v", entries)
|
||||
}
|
||||
if resolution.MaterialItemID != 10007367 || resolution.MaterialCountPerUse != 1 {
|
||||
t.Fatalf("material=%d x%d", resolution.MaterialItemID, resolution.MaterialCountPerUse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxBoosterFamily(t *testing.T) {
|
||||
catalog, source := newMagicBoxTestCatalog(t)
|
||||
resolution, err := resolveCurrentMagicBox(catalog, source, 2001)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve error = %v", err)
|
||||
}
|
||||
if resolution.Kind != "random" || len(resolution.Groups) != 1 {
|
||||
t.Fatalf("resolution = %+v", resolution)
|
||||
}
|
||||
group := resolution.Groups[0]
|
||||
if group.DrawCount != 2 {
|
||||
t.Fatalf("draw count = %d, want leading DrawCount 2", group.DrawCount)
|
||||
}
|
||||
if len(group.Entries) != 2 || group.Entries[0].ItemID != 3001 || group.Entries[0].Count != 1 || group.Entries[1].ItemID != 3002 || group.Entries[1].Count != 2 {
|
||||
t.Fatalf("entries = %+v", group.Entries)
|
||||
}
|
||||
if resolution.MaterialItemID != 10007367 || resolution.MaterialCountPerUse != 1 {
|
||||
t.Fatalf("material = %d x%d, want [need material] fallback", resolution.MaterialItemID, resolution.MaterialCountPerUse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxUnsupportedAndUnknown(t *testing.T) {
|
||||
catalog, source := newMagicBoxTestCatalog(t)
|
||||
resolution, err := resolveCurrentMagicBox(catalog, source, 2002)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve error = %v", err)
|
||||
}
|
||||
if resolution.Kind != "" {
|
||||
t.Fatalf("plain stackable kind = %q, want empty", resolution.Kind)
|
||||
}
|
||||
resolution, err = resolveCurrentMagicBox(catalog, source, 9999)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve error = %v", err)
|
||||
}
|
||||
if resolution.Kind != "" {
|
||||
t.Fatalf("unknown item kind = %q, want empty", resolution.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentMagicBoxRewardItemMetadata(t *testing.T) {
|
||||
catalog, source := newMagicBoxTestCatalog(t)
|
||||
before := time.Now().UTC()
|
||||
item, err := resolveCurrentMagicBoxRewardItem(catalog, source, 2600014)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve error = %v", err)
|
||||
}
|
||||
if item.ItemID != 2600014 || item.Kind != "stackable" || item.StackLimit != 100 || !item.Seal || item.UsablePeriodDays != 30 {
|
||||
t.Fatalf("reward item = %+v", item)
|
||||
}
|
||||
minExpire := time.Unix(before.Unix()+30*86400, 0).UTC()
|
||||
maxExpire := time.Unix(time.Now().UTC().Unix()+30*86400, 0).UTC()
|
||||
if item.ExpireAt.Before(minExpire) || item.ExpireAt.After(maxExpire) {
|
||||
t.Fatalf("reward expiration=%s outside %s..%s", item.ExpireAt, minExpire, maxExpire)
|
||||
}
|
||||
if item.SlotStart != 65 || item.SlotEnd != 120 {
|
||||
t.Fatalf("slot range = %d-%d, want 65-120", item.SlotStart, item.SlotEnd)
|
||||
}
|
||||
if item.PVFPath != "stackable/potion/ptn.stk" {
|
||||
t.Fatalf("path = %q", item.PVFPath)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user