171 lines
5.5 KiB
Go
171 lines
5.5 KiB
Go
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
|
||
}
|