Files
90CNServer/server/internal/services/dnfbridge/epic_wish_drop.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

86 lines
3.3 KiB
Go

package dnfbridge
// currentEpicWishHit* scale the Epic wish preference branch. The branch runs
// only AFTER the Hell table has already selected rarity 4, so it never changes
// the Epic rarity roll itself -- it only decides which Epic is handed out.
//
// The numerator is a product dial, not a PVF value. Measured against the live
// runtime Script.pvf on 2026-09-23: etc/epicpieceinfo.etc holds 966 entries
// across six level buckets (65/70/75/80/85/90), so the level-90 band [85,90]
// carries roughly 264 candidates with a total creation-rate weight of roughly
// 30,900; one average Epic is therefore only ~0.38% of the band and the
// heaviest observed weight (250) is ~0.81%.
//
// Because the miss branch REMOVES every wished entry from the pool, a valid
// wish does not add to its item's natural share -- it REPLACES it with
// numerator/denominator/N (N = filled wish slots, 1..5). The former 25/100
// therefore pinned a single wish at 25% of all Epic drops, about 66x its
// natural share. 2/100 keeps a single wish a 2.5x..20x gain while a fully
// filled five-slot list lands at 0.4% per slot, i.e. near -- and for the
// heavier entries slightly below -- the natural share.
const (
currentEpicWishHitNumerator = 2
currentEpicWishHitDenominator = 100
)
func selectCurrentTimeRiftEpicCandidate(
rng *currentDungeonDropLCG,
pools map[int][]currentDungeonHellPartyFragmentCandidate,
monsterLevel int,
wishItems []uint32,
) (currentDungeonHellPartyFragmentCandidate, bool, bool) {
if rng == nil {
return currentDungeonHellPartyFragmentCandidate{}, false, false
}
candidates := make([]currentDungeonHellPartyFragmentCandidate, 0)
byEquipment := make(map[uint32]currentDungeonHellPartyFragmentCandidate)
for _, level := range currentHellPartyEpicLevels(monsterLevel) {
for _, candidate := range pools[level] {
candidates = append(candidates, candidate)
if candidate.EquipmentID != 0 {
byEquipment[candidate.EquipmentID] = candidate
}
}
}
if len(candidates) == 0 {
return currentDungeonHellPartyFragmentCandidate{}, false, false
}
validWish := make([]currentDungeonHellPartyFragmentCandidate, 0, len(wishItems))
wished := make(map[uint32]struct{}, len(wishItems))
for _, itemID := range wishItems {
candidate, found := byEquipment[itemID]
if itemID == 0 || !found {
continue
}
if _, duplicate := wished[itemID]; duplicate {
continue
}
wished[itemID] = struct{}{}
validWish = append(validWish, candidate)
}
if len(validWish) != 0 &&
rng.Next(currentEpicWishHitDenominator) < currentEpicWishHitNumerator {
return validWish[rng.Next(len(validWish))], true, true
}
normal := make([]currentDungeonHellPartyFragmentCandidate, 0, len(candidates))
for _, candidate := range candidates {
if _, excluded := wished[candidate.EquipmentID]; !excluded {
normal = append(normal, candidate)
}
}
if candidate, found := selectCurrentHellPartyFragmentCandidateFromPool(rng, normal); found {
return candidate, true, false
}
// If every eligible Epic is wished, the archived miss branch had an empty
// pool and silently erased an already-successful rarity-4 result. Preserve
// that Epic roll by falling back to the complete weighted pool.
candidate, found := selectCurrentHellPartyFragmentCandidateFromPool(rng, candidates)
if !found {
return currentDungeonHellPartyFragmentCandidate{}, false, false
}
return candidate, true, false
}