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