服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
86 lines
3.3 KiB
Go
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
|
|
}
|