DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

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

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

No files matched your search

@@ -0,0 +1,250 @@
package companion
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strconv"
"strings"
)
// StrategyCapability is a named primitive that the offline compiler is
// allowed to consume. Keeping this list explicit prevents a Lua profile from
// injecting arbitrary AIC tags or silently claiming a missing client feature.
type StrategyCapability string
const (
CapabilityThinkTerm StrategyCapability = "think_term"
CapabilityDistance StrategyCapability = "target_distance"
CapabilityCooldown StrategyCapability = "key_cooldown"
CapabilityActionStream StrategyCapability = "action_stream"
CapabilityFriendAnchor StrategyCapability = "friend_anchor"
CapabilityPartyHP StrategyCapability = "party_hp"
CapabilityEnemyCount StrategyCapability = "enemy_count_in_area"
CapabilityLeaderFollow StrategyCapability = "leader_follow"
CapabilityLeaderBuff StrategyCapability = "leader_buff"
CapabilityActionAck StrategyCapability = "action_ack"
CapabilityTargetSticky StrategyCapability = "target_sticky"
CapabilityStuckDetour StrategyCapability = "stuck_detour"
)
// StrategyCapabilities describes the currently proven compiler/runtime
// surface. The defaults reflect the AIC tokens found in the checked-in PVF;
// the dynamic leader/action features remain intentionally false until a real
// client implementation proves them.
type StrategyCapabilities struct {
ThinkTerm bool `json:"think_term"`
Distance bool `json:"target_distance"`
Cooldown bool `json:"key_cooldown"`
ActionStream bool `json:"action_stream"`
FriendAnchor bool `json:"friend_anchor"`
PartyHP bool `json:"party_hp"`
EnemyCount bool `json:"enemy_count_in_area"`
LeaderFollow bool `json:"leader_follow"`
LeaderBuff bool `json:"leader_buff"`
ActionAck bool `json:"action_ack"`
TargetSticky bool `json:"target_sticky"`
StuckDetour bool `json:"stuck_detour"`
}
func DefaultStrategyCapabilities() StrategyCapabilities {
return StrategyCapabilities{
ThinkTerm: true, Distance: true, Cooldown: true, ActionStream: true,
FriendAnchor: true, PartyHP: true, EnemyCount: true,
LeaderFollow: false, LeaderBuff: false, ActionAck: false,
TargetSticky: false, StuckDetour: false,
}
}
func (c StrategyCapabilities) Has(cap StrategyCapability) bool {
switch cap {
case CapabilityThinkTerm:
return c.ThinkTerm
case CapabilityDistance:
return c.Distance
case CapabilityCooldown:
return c.Cooldown
case CapabilityActionStream:
return c.ActionStream
case CapabilityFriendAnchor:
return c.FriendAnchor
case CapabilityPartyHP:
return c.PartyHP
case CapabilityEnemyCount:
return c.EnemyCount
case CapabilityLeaderFollow:
return c.LeaderFollow
case CapabilityLeaderBuff:
return c.LeaderBuff
case CapabilityActionAck:
return c.ActionAck
case CapabilityTargetSticky:
return c.TargetSticky
case CapabilityStuckDetour:
return c.StuckDetour
default:
return false
}
}
type StrategyStep struct {
Name string `json:"name"`
Priority int `json:"priority"`
Value int `json:"value,omitempty"`
}
// CompiledStrategy is an offline artifact. It can be written into a
// generated AIC/PVF overlay by the companionpvf tool, but it is never marked
// as applied until a future client executor reports a real action receipt.
type CompiledStrategy struct {
Status string `json:"status"`
LuaSource string `json:"lua_source"`
LuaSHA256 string `json:"lua_sha256"`
Steps []StrategyStep `json:"steps"`
MissingCapabilities []StrategyCapability `json:"missing_capabilities,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
func (r CompiledStrategy) ReadyForStaticAIC() bool {
return r.Status == "compiled_static" || r.Status == "compiled_with_gaps"
}
// CompileAIProfile converts the high-level GM profile to a small, auditable
// strategy IR and a Lua configuration source. The IR is deliberately limited
// to known decisions; unproven dynamic features are reported as gaps instead
// of being silently treated as live behaviour.
func CompileAIProfile(profile AIProfile, caps StrategyCapabilities) (CompiledStrategy, error) {
profile = NormalizeAIProfile(profile)
if err := profile.Validate(); err != nil {
return CompiledStrategy{}, err
}
tuning := EffectiveAITuning(profile.Tuning)
steps := []StrategyStep{
{Name: "think", Priority: 100, Value: tuning.ThinkIntervalMS},
{Name: "vision", Priority: 95, Value: tuning.VisionRange},
{Name: "keep_distance", Priority: 90, Value: tuning.FollowDistance},
}
missing := make(map[StrategyCapability]struct{})
warnings := []string{}
need := func(cap StrategyCapability, step StrategyStep, warning string) {
if caps.Has(cap) {
steps = append(steps, step)
return
}
missing[cap] = struct{}{}
if warning != "" {
warnings = append(warnings, warning)
}
}
if profile.Behavior == "assist" {
need(CapabilityFriendAnchor,
StrategyStep{Name: "assist_anchor", Priority: 85, Value: tuning.AssistLeashX},
"辅助站位使用已验证的 friend anchor 近似;固定队长后方坐标仍需客户端能力。")
need(CapabilityLeaderFollow,
StrategyStep{Name: "follow_leader", Priority: 84, Value: tuning.FollowDistance},
"当前没有经过验证的队长坐标读取,未宣称实时躲在主角后方。")
if profile.BuffPolicy != AIBuffPolicyOff {
need(CapabilityLeaderBuff,
StrategyStep{Name: "buff_leader", Priority: 120},
"BUFF 优先级已保存,但当前编译器没有真实队长施法回执。")
}
if tuning.HealLeaderPercent > 0 {
need(CapabilityPartyHP,
StrategyStep{Name: "heal_leader", Priority: 118, Value: tuning.HealLeaderPercent},
"治疗阈值可编译为 party hp 条件;技能实际生效仍需 AIC/PVF 回读。")
}
steps = append(steps, StrategyStep{Name: "assist_attack_gate", Priority: 40, Value: tuning.AssistAttackIntervalMS})
} else {
if profile.BuffPolicy != AIBuffPolicyOff {
need(CapabilityActionStream,
StrategyStep{Name: "buff_first", Priority: 120},
"先 BUFF 后攻击仅能生成有序技能流,当前没有动作回执。")
}
steps = append(steps, StrategyStep{Name: "attack_rotation", Priority: 70, Value: tuning.ChaseRange})
}
if profile.ComboEnabled {
need(CapabilityCooldown,
StrategyStep{Name: "combo_rotation", Priority: 80},
"职业连招使用白名单 key stream;没有冷却原语时拒绝生成。")
}
if profile.AOEMinTargets >= 2 {
need(CapabilityEnemyCount,
StrategyStep{Name: "aoe_when_many", Priority: 105, Value: profile.AOEMinTargets},
"范围技能条件依赖已验证的区域敌人数;技能键仍由职业模板提供。")
}
if tuning.TargetStickinessMS > 0 {
need(CapabilityTargetSticky, StrategyStep{Name: "target_sticky", Priority: 60, Value: tuning.TargetStickinessMS},
"目标黏性参数已保存,当前 AIC 没有可证明的目标锁定回执。")
}
if tuning.StuckMS > 0 || tuning.DetourMS > 0 || tuning.GiveUpMS > 0 {
need(CapabilityStuckDetour, StrategyStep{Name: "stuck_detour_give_up", Priority: 30, Value: tuning.GiveUpMS},
"卡住重试、绕行和放弃目标等待客户端位置/目标状态。")
}
missingList := make([]StrategyCapability, 0, len(missing))
for cap := range missing {
missingList = append(missingList, cap)
}
sort.Slice(missingList, func(i, j int) bool { return missingList[i] < missingList[j] })
sort.Slice(steps, func(i, j int) bool {
if steps[i].Priority != steps[j].Priority {
return steps[i].Priority > steps[j].Priority
}
return steps[i].Name < steps[j].Name
})
lua := renderLuaPolicy(profile, tuning, steps)
hash := sha256.Sum256([]byte(lua))
status := "compiled_static"
if len(missingList) > 0 {
status = "compiled_with_gaps"
}
return CompiledStrategy{
Status: status, LuaSource: lua, LuaSHA256: hex.EncodeToString(hash[:]),
Steps: steps, MissingCapabilities: missingList, Warnings: warnings,
}, nil
}
func luaString(value string) string {
var b strings.Builder
b.WriteByte('"')
for _, r := range value {
switch r {
case '\\':
b.WriteString("\\\\")
case '"':
b.WriteString("\\\"")
case '\n':
b.WriteString("\\n")
case '\r':
b.WriteString("\\r")
case '\t':
b.WriteString("\\t")
default:
if r < 32 || r == 127 {
fmt.Fprintf(&b, "\\%03d", r)
} else {
b.WriteRune(r)
}
}
}
b.WriteByte('"')
return b.String()
}
func renderLuaPolicy(profile AIProfile, tuning AITuning, steps []StrategyStep) string {
var b strings.Builder
b.WriteString("-- generated by companion strategy compiler; data only\nreturn {\n")
fmt.Fprintf(&b, " version = 1, behavior = %s, class_id = %d, buff_policy = %s, combo_preset = %s, combo_enabled = %s, aoe_min_targets = %d, protect_leader = %s,\n",
luaString(profile.Behavior), profile.ClassID, luaString(profile.BuffPolicy), luaString(profile.ComboPreset), strconv.FormatBool(profile.ComboEnabled), profile.AOEMinTargets, strconv.FormatBool(profile.ProtectLeader))
fmt.Fprintf(&b, " tuning = { think_interval_ms = %d, assist_attack_interval_ms = %d, assist_leash_x = %d, assist_leash_y = %d, chase_range = %d, vision_range = %d, target_stickiness_ms = %d, heal_leader_percent = %d, heal_self_percent = %d, retry_ms = %d, support_retry_ms = %d, busy_move_ms = %d, stuck_ms = %d, detour_ms = %d, give_up_ms = %d, follow_distance = %d },\n",
tuning.ThinkIntervalMS, tuning.AssistAttackIntervalMS, tuning.AssistLeashX, tuning.AssistLeashY, tuning.ChaseRange, tuning.VisionRange, tuning.TargetStickinessMS, tuning.HealLeaderPercent, tuning.HealSelfPercent, tuning.RetryMS, tuning.SupportRetryMS, tuning.BusyMoveMS, tuning.StuckMS, tuning.DetourMS, tuning.GiveUpMS, tuning.FollowDistance)
b.WriteString(" steps = {\n")
for _, step := range steps {
fmt.Fprintf(&b, " { name = %s, priority = %d, value = %d },\n", luaString(step.Name), step.Priority, step.Value)
}
b.WriteString(" },\n}\n")
return b.String()
}