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,137 @@
|
||||
package companion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AITemplateSelector binds one saved profile to a verified AICharacter.lst
|
||||
// entry. The selector is part of the offline contract; it does not imply
|
||||
// that the current client has loaded or executed the entry.
|
||||
type AITemplateSelector struct {
|
||||
Key string `json:"key"`
|
||||
Job uint8 `json:"job"`
|
||||
ClassID uint16 `json:"class_id"`
|
||||
Behavior string `json:"behavior"`
|
||||
ComboPreset string `json:"combo_preset"`
|
||||
APCCode int64 `json:"apc_code"`
|
||||
AICPath string `json:"aic_path"`
|
||||
BaseAICPath string `json:"base_aic_path"`
|
||||
Legacy bool `json:"legacy"`
|
||||
}
|
||||
|
||||
type aiTemplateVariant struct {
|
||||
Code int64
|
||||
Path string
|
||||
}
|
||||
|
||||
type aiTemplatePair struct {
|
||||
Profession string
|
||||
Assist aiTemplateVariant
|
||||
Aggressive aiTemplateVariant
|
||||
}
|
||||
|
||||
// These are the stable APC/AIC pairs selected by the checked-in client native
|
||||
// projection contract (90CN_companion_p0.h) and verified against the current
|
||||
// AICharacter.lst. The server must patch the AIC that the client actually
|
||||
// spawns. The old friendly-doll entries (for example christian_ronaldo and
|
||||
// yugo) are valid archive assets, but they are not the active baseline APCs
|
||||
// used by the native companion projection and therefore cannot change live
|
||||
// behaviour.
|
||||
//
|
||||
// Assist and aggressive profiles intentionally share the active baseline AIC
|
||||
// for a job. The profile-specific action/destination/move programs are
|
||||
// compiled into that AIC; the caller must build/select one profile for a
|
||||
// baseline at a time because a single PVF cannot contain two different
|
||||
// programs at the same archive path.
|
||||
var aiTemplatePairs = map[uint8]aiTemplatePair{
|
||||
0: {"swordman", aiTemplateVariant{900, "aicharacter/_bizarre/swordman/mirror_swordman/mirror_swordman.aic"}, aiTemplateVariant{900, "aicharacter/_bizarre/swordman/mirror_swordman/mirror_swordman.aic"}},
|
||||
1: {"fighter", aiTemplateVariant{5405, "aicharacter/fighter/arad_fighter/arad_fighter_doll.aic"}, aiTemplateVariant{5405, "aicharacter/fighter/arad_fighter/arad_fighter_doll.aic"}},
|
||||
2: {"gunner", aiTemplateVariant{10903, "aicharacter/_bizarre/gunner/mirror_gunner/mirror_gunner.aic"}, aiTemplateVariant{10903, "aicharacter/_bizarre/gunner/mirror_gunner/mirror_gunner.aic"}},
|
||||
3: {"mage", aiTemplateVariant{15902, "aicharacter/_bizarre/magician/mirror_magician/mirror_magician.aic"}, aiTemplateVariant{15902, "aicharacter/_bizarre/magician/mirror_magician/mirror_magician.aic"}},
|
||||
4: {"priest", aiTemplateVariant{20902, "aicharacter/_bizarre/priest/mirror_priest/mirror_priest.aic"}, aiTemplateVariant{20902, "aicharacter/_bizarre/priest/mirror_priest/mirror_priest.aic"}},
|
||||
5: {"atgunner", aiTemplateVariant{25900, "aicharacter/_bizarre/atgunner/mirror_atgunner/mirror_atgunner.aic"}, aiTemplateVariant{25900, "aicharacter/_bizarre/atgunner/mirror_atgunner/mirror_atgunner.aic"}},
|
||||
6: {"thief", aiTemplateVariant{30901, "aicharacter/_bizarre/Thief/mirror_Thief/mirror_Thief.aic"}, aiTemplateVariant{30901, "aicharacter/_bizarre/Thief/mirror_Thief/mirror_Thief.aic"}},
|
||||
7: {"atfighter", aiTemplateVariant{35903, "aicharacter/_bizarre/atfighter/mirror_atfighter/mirror_atfighter.aic"}, aiTemplateVariant{35903, "aicharacter/_bizarre/atfighter/mirror_atfighter/mirror_atfighter.aic"}},
|
||||
8: {"atmage", aiTemplateVariant{40800, "aicharacter/autocharacter/atmage/auto1/auto_atmage1.aic"}, aiTemplateVariant{40800, "aicharacter/autocharacter/atmage/auto1/auto_atmage1.aic"}},
|
||||
9: {"demonic_swordman", aiTemplateVariant{902, "aicharacter/_bizarre/swordman/despair_darkknight/despair_darkknight.aic"}, aiTemplateVariant{902, "aicharacter/_bizarre/swordman/despair_darkknight/despair_darkknight.aic"}},
|
||||
10: {"creator", aiTemplateVariant{15903, "aicharacter/_bizarre/magician/despair_creator/despair_creator.aic"}, aiTemplateVariant{15903, "aicharacter/_bizarre/magician/despair_creator/despair_creator.aic"}},
|
||||
11: {"atswordman", aiTemplateVariant{56901, "aicharacter/_bizarre/atswordman/despair_atswordman/despair_atswordman.aic"}, aiTemplateVariant{56901, "aicharacter/_bizarre/atswordman/despair_atswordman/despair_atswordman.aic"}},
|
||||
12: {"knight", aiTemplateVariant{60901, "aicharacter/_bizarre/knight/despair_knight/despair_knight.aic"}, aiTemplateVariant{60901, "aicharacter/_bizarre/knight/despair_knight/despair_knight.aic"}},
|
||||
13: {"demonic_lancer", aiTemplateVariant{65001, "aicharacter/demoniclancer/mirror_demonicLancer/mirror_demonicLancer.aic"}, aiTemplateVariant{65001, "aicharacter/demoniclancer/mirror_demonicLancer/mirror_demonicLancer.aic"}},
|
||||
// job 14(女圣职者)必须保持 70003 `atpriest/despair_atpriest.aic`,**不要改成 70002**。
|
||||
//
|
||||
// 曾试过改成 70002 `atpriest/mirror_demoniclancer.aic`,实测完全无效。原因是
|
||||
// 选哪个 APC 由**客户端内置基线表**决定,服务端的 ai_template 只决定“补哪条 AIC”。
|
||||
//
|
||||
// 90CN_companion_p0.h 的基线表格式是 {job, sourceGrow, apcCode, level, aicJob, aicGrow}:
|
||||
//
|
||||
// {14,0,70003,70,14,0}, // <- stable 外壳(sourceGrow=0 且 aicGrow=0)
|
||||
// {14,1,71200,86,14,1},
|
||||
// {14,2,71203,86,14,2},
|
||||
//
|
||||
// ProjectionBaselineContract::TrySelect 的逻辑是
|
||||
//
|
||||
// selected = stable ? *stable : *exact; // 优先 grow=0 的稳定外壳
|
||||
//
|
||||
// 所以奶妈 grow_type=33(firstGrow=1, awakening=2)虽然 exact 命中 {14,1,71200},
|
||||
// 最终仍被 stable 抢走,**客户端永远用 APC 70003 的 AIC**。服务端把模板改成 70002
|
||||
// 只会让覆盖打到一条没人用的 AIC 上,反而让 70003 丢掉 12 格快捷栏程序 —— 表现为
|
||||
// “能召唤但不放技能、不攻击”。
|
||||
//
|
||||
// 另外注意:外壳 APC 固定是 grow=0 的形态,客户端只把源角色的 grow/觉醒/技能桥接
|
||||
// 到 AIC 的每实例副本上,**视觉职业仍然是那个 grow=0 外壳**。所以支援兵看起来
|
||||
// “未觉醒”是这套投影设计的固有限制,不是模板选错。
|
||||
14: {"atpriest", aiTemplateVariant{70003, "aicharacter/atpriest/despair_atpriest/despair_atpriest.aic"}, aiTemplateVariant{70003, "aicharacter/atpriest/despair_atpriest/despair_atpriest.aic"}},
|
||||
15: {"gunblader", aiTemplateVariant{75001, "aicharacter/gunblader/mirror_gunblader/mirror_gunblader.aic"}, aiTemplateVariant{75001, "aicharacter/gunblader/mirror_gunblader/mirror_gunblader.aic"}},
|
||||
}
|
||||
|
||||
func SelectAITemplate(profile AIProfile, job uint16) AITemplateSelector {
|
||||
profile = NormalizeAIProfile(profile)
|
||||
job8 := uint8(job)
|
||||
pair, ok := aiTemplatePairs[job8]
|
||||
if !ok {
|
||||
job8 = 1
|
||||
pair = aiTemplatePairs[job8]
|
||||
}
|
||||
classID := profile.ClassID
|
||||
if classID == 0 {
|
||||
classID = job
|
||||
}
|
||||
variant := pair.Assist
|
||||
if profile.Behavior == "aggressive" {
|
||||
variant = pair.Aggressive
|
||||
}
|
||||
return AITemplateSelector{
|
||||
Key: fmt.Sprintf("%s/%s/job-%02d/class-%d/%s", pair.Profession, profile.Behavior, job8, classID, artifactSlug(profile.ComboPreset)),
|
||||
Job: job8,
|
||||
ClassID: classID,
|
||||
Behavior: profile.Behavior,
|
||||
ComboPreset: profile.ComboPreset,
|
||||
APCCode: variant.Code,
|
||||
AICPath: variant.Path,
|
||||
BaseAICPath: variant.Path,
|
||||
Legacy: variant.Code == 5405,
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveAITemplate is the descriptive name used by server projection code;
|
||||
// SelectAITemplate remains the compiler-facing alias.
|
||||
func ResolveAITemplate(profile AIProfile, job uint16) AITemplateSelector {
|
||||
return SelectAITemplate(profile, job)
|
||||
}
|
||||
|
||||
func ValidateAITemplateSelector(selector AITemplateSelector) error {
|
||||
if strings.TrimSpace(selector.Key) == "" || selector.APCCode <= 0 || selector.AICPath == "" {
|
||||
return ErrSelection
|
||||
}
|
||||
want := SelectAITemplate(AIProfile{Behavior: selector.Behavior, ClassID: selector.ClassID, ComboPreset: selector.ComboPreset}, uint16(selector.Job))
|
||||
if selector.APCCode != want.APCCode || !strings.EqualFold(selector.AICPath, want.AICPath) {
|
||||
return ErrSelection
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s AITemplateSelector) Valid() bool {
|
||||
return ValidateAITemplateSelector(s) == nil
|
||||
}
|
||||
Reference in new issue
Block a user