package model import ( "blazing/cool" "fmt" "strings" "github.com/dop251/goja" ) const ( TableNameBossConfig = "config_boss" // BOSS配置表(全量包含基础/奖励/护盾/捕捉/特效/世界野怪/地图费用/战斗通用逻辑) ) // BossConfig BOSS配置模型(覆盖所有补充的配置项:GBTL/非VIP费用/首场景/战斗通用逻辑) type BossConfig struct { *cool.Model // 嵌入通用Model(包含ID/创建时间/更新时间等通用字段) PetBaseConfig MapID int32 `gorm:"not null;index;comment:'所属地图ID'" json:"map_id" description:"地图ID"` Ordernum int32 `gorm:"not null;default:0;comment:'排序'" json:"ordernum" description:"排序"` ParentID int32 `gorm:"not null;default:0;column:parentId;type:int" json:"parentId"` // 父ID Remark string `gorm:"comment:'BOSS备注'" json:"remark"` //是否可捕捉MapPit IsCapture int `gorm:"type:int;default:0;comment:'是否可捕捉'" json:"is_capture"` Script string `gorm:"size:1024;default:'';comment:'BOSS脚本'" json:"script"` //boss出招逻辑做成js脚本 Rule []uint32 `gorm:"type:jsonb; ;comment:'战胜规则'" json:"rule"` } // BossHookSkillContext 为脚本暴露当前精灵技能可用信息。 type BossHookSkillContext struct { SkillID uint32 `json:"skill_id"` PP uint32 `json:"pp"` CanUse bool `json:"can_use"` } // BossHookPetContext 为脚本暴露战斗中双方精灵简要信息。 type BossHookPetContext struct { PetID uint32 `json:"pet_id"` CatchTime uint32 `json:"catch_time"` Hp uint32 `json:"hp"` MaxHp uint32 `json:"max_hp"` } // BossHookAttackContext 参考 AttackValue,为脚本暴露关键战斗面板/结果字段。 type BossHookAttackContext struct { SkillID uint32 `json:"skill_id"` AttackTime uint32 `json:"attack_time"` IsCritical uint32 `json:"is_critical"` LostHp uint32 `json:"lost_hp"` GainHp int32 `json:"gain_hp"` RemainHp int32 `json:"remain_hp"` MaxHp uint32 `json:"max_hp"` State uint32 `json:"state"` Offensive float32 `json:"offensive"` Status []int8 `json:"status"` Prop []int8 `json:"prop"` } // BossHookActionContext 为 boss 脚本提供可读写的出手上下文。 type BossHookActionContext struct { HookAction bool `json:"hookaction"` // effect 链原始 HookAction 判定 Round uint32 `json:"round"` // 当前回合数 IsFirst bool `json:"is_first"` // 是否先手 Our *BossHookPetContext `json:"our"` // 我方当前精灵 Opp *BossHookPetContext `json:"opp"` // 对方当前精灵 Skills []BossHookSkillContext `json:"skills"` // 我方技能 OurAttack *BossHookAttackContext `json:"our_attack"` // 我方AttackValue快照 OppAttack *BossHookAttackContext `json:"opp_attack"` // 对方AttackValue快照 Action string `json:"action"` // auto/skill/switch SkillID uint32 `json:"skill_id"` // action=skill CatchTime uint32 `json:"catch_time"` // action=switch UseSkillFn func(skillID uint32) `json:"-"` SwitchPetFn func(catchTime uint32) `json:"-"` } func (*BossConfig) TableName() string { return TableNameBossConfig } func (*BossConfig) GroupName() string { return "default" } func NewBossConfig() *BossConfig { return &BossConfig{Model: cool.NewModel()} } func init() { cool.CreateTable(&BossConfig{}) } // RunHookActionScript 执行 BOSS 脚本 hookAction。 func (b *BossConfig) RunHookActionScript(hookAction any) (bool, error) { if b == nil || strings.TrimSpace(b.Script) == "" { return true, nil } program, err := goja.Compile("boss_hook_action.js", b.Script, false) if err != nil { return false, fmt.Errorf("compile boss script: %w", err) } vm := goja.New() vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true)) bindBossScriptFunctions(vm, hookAction) if _, err = vm.RunProgram(program); err != nil { return false, fmt.Errorf("run boss script: %w", err) } var ( callable goja.Callable ok bool ) for _, fnName := range []string{"hookAction", "HookAction", "hookaction"} { callable, ok = goja.AssertFunction(vm.Get(fnName)) if ok { break } } if !ok { return false, fmt.Errorf("boss script function not found: hookAction") } result, err := callable(goja.Undefined(), vm.ToValue(hookAction)) if err != nil { return false, fmt.Errorf("execute boss hookAction: %w", err) } if goja.IsUndefined(result) || goja.IsNull(result) { return defaultHookActionResult(hookAction), nil } return result.ToBoolean(), nil } func bindBossScriptFunctions(vm *goja.Runtime, hookAction any) { ctx, ok := hookAction.(*BossHookActionContext) if !ok || ctx == nil { return } _ = vm.Set("useSkill", func(call goja.FunctionCall) goja.Value { if ctx.UseSkillFn == nil || len(call.Arguments) == 0 { return goja.Undefined() } skillID := call.Arguments[0].ToInteger() if skillID < 0 { return goja.Undefined() } ctx.UseSkillFn(uint32(skillID)) return goja.Undefined() }) _ = vm.Set("switchPet", func(call goja.FunctionCall) goja.Value { if ctx.SwitchPetFn == nil || len(call.Arguments) == 0 { return goja.Undefined() } catchTime := call.Arguments[0].ToInteger() if catchTime < 0 { return goja.Undefined() } ctx.SwitchPetFn(uint32(catchTime)) return goja.Undefined() }) } func defaultHookActionResult(hookAction any) bool { if ctx, ok := hookAction.(*BossHookActionContext); ok { return ctx.HookAction } if val, ok := hookAction.(bool); ok { return val } return true }