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,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()
|
||||
}
|
||||
Reference in new issue
Block a user