feat: 增强 Boss 脚本 HookAction 接入能力
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
引入 BossHookActionContext 封装战斗上下文,并支持脚本调用 useSkill 和 switchPet 函数控制战斗行为。
This commit is contained in:
173
docs/boss-script-hookaction-guide-2026-04-05.md
Normal file
173
docs/boss-script-hookaction-guide-2026-04-05.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Boss Script(HookAction)接入说明
|
||||||
|
|
||||||
|
日期:2026-04-05
|
||||||
|
|
||||||
|
## 1. 执行流程
|
||||||
|
|
||||||
|
1. 先执行战斗效果链 `HookAction()`
|
||||||
|
2. 执行脚本 `hookAction(hookaction)`
|
||||||
|
3. 用脚本返回值决定是否继续出手
|
||||||
|
4. 脚本可直接调用 Go 绑定函数:`useSkill()`、`switchPet()`
|
||||||
|
|
||||||
|
## 2. JS 可调用的 Go 函数
|
||||||
|
|
||||||
|
1. `useSkill(skillId: number)`
|
||||||
|
- 作用:指定本回合使用技能
|
||||||
|
- 示例:`useSkill(5001)`
|
||||||
|
|
||||||
|
2. `switchPet(catchTime: number)`
|
||||||
|
- 作用:指定本回合切换精灵
|
||||||
|
- 示例:`switchPet(2)`
|
||||||
|
|
||||||
|
## 3. `hookaction` 参数字段
|
||||||
|
|
||||||
|
1. `hookaction.hookaction: boolean`
|
||||||
|
- effect 链原始判定
|
||||||
|
2. `hookaction.round: number`
|
||||||
|
- 当前回合数
|
||||||
|
3. `hookaction.is_first: boolean`
|
||||||
|
- 是否先手
|
||||||
|
4. `hookaction.our: { pet_id, catch_time, hp, max_hp } | null`
|
||||||
|
- 我方当前精灵
|
||||||
|
5. `hookaction.opp: { pet_id, catch_time, hp, max_hp } | null`
|
||||||
|
- 对方当前精灵
|
||||||
|
6. `hookaction.skills: Array<{ skill_id, pp, can_use }>`
|
||||||
|
- 我方当前技能快照
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
|
||||||
|
- `true`:继续行动
|
||||||
|
- `false`:阻止行动
|
||||||
|
- 不返回:默认回退到 `hookaction.hookaction`
|
||||||
|
|
||||||
|
## 4. 脚本示例
|
||||||
|
|
||||||
|
### 4.1 第3回合固定放技能
|
||||||
|
|
||||||
|
```js
|
||||||
|
function hookAction(hookaction) {
|
||||||
|
if (!hookaction.hookaction) return false;
|
||||||
|
|
||||||
|
if (hookaction.round === 3) {
|
||||||
|
useSkill(5001);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 低血切宠
|
||||||
|
|
||||||
|
```js
|
||||||
|
function hookAction(hookaction) {
|
||||||
|
if (!hookaction.hookaction) return false;
|
||||||
|
if (!hookaction.our) return true;
|
||||||
|
|
||||||
|
var hpRate = hookaction.our.max_hp > 0 ? hookaction.our.hp / hookaction.our.max_hp : 1;
|
||||||
|
if (hpRate < 0.3) {
|
||||||
|
switchPet(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 读取技能可用性后出招
|
||||||
|
|
||||||
|
```js
|
||||||
|
function hookAction(hookaction) {
|
||||||
|
if (!hookaction.hookaction) return false;
|
||||||
|
|
||||||
|
for (var i = 0; i < hookaction.skills.length; i++) {
|
||||||
|
var s = hookaction.skills[i];
|
||||||
|
if (s.can_use && s.skill_id === 5001) {
|
||||||
|
useSkill(s.skill_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Monaco 类型提示
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import * as monaco from "monaco-editor";
|
||||||
|
|
||||||
|
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||||
|
allowNonTsExtensions: true,
|
||||||
|
checkJs: true,
|
||||||
|
target: monaco.languages.typescript.ScriptTarget.ES2020,
|
||||||
|
});
|
||||||
|
|
||||||
|
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
||||||
|
`
|
||||||
|
interface BossHookPetContext {
|
||||||
|
pet_id: number;
|
||||||
|
catch_time: number;
|
||||||
|
hp: number;
|
||||||
|
max_hp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BossHookSkillContext {
|
||||||
|
skill_id: number;
|
||||||
|
pp: number;
|
||||||
|
can_use: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BossHookActionContext {
|
||||||
|
hookaction: boolean;
|
||||||
|
round: number;
|
||||||
|
is_first: boolean;
|
||||||
|
our: BossHookPetContext | null;
|
||||||
|
opp: BossHookPetContext | null;
|
||||||
|
skills: BossHookSkillContext[];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function hookAction(hookaction: BossHookActionContext): boolean;
|
||||||
|
declare function HookAction(hookaction: BossHookActionContext): boolean;
|
||||||
|
declare function hookaction(hookaction: BossHookActionContext): boolean;
|
||||||
|
|
||||||
|
declare function useSkill(skillId: number): void;
|
||||||
|
declare function switchPet(catchTime: number): void;
|
||||||
|
`,
|
||||||
|
"ts:boss-script.d.ts"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Monaco 补全(可选)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
monaco.languages.registerCompletionItemProvider("javascript", {
|
||||||
|
provideCompletionItems() {
|
||||||
|
return {
|
||||||
|
suggestions: [
|
||||||
|
{
|
||||||
|
label: "boss hookAction",
|
||||||
|
kind: monaco.languages.CompletionItemKind.Snippet,
|
||||||
|
insertTextRules:
|
||||||
|
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||||
|
insertText: [
|
||||||
|
"function hookAction(hookaction) {",
|
||||||
|
" if (!hookaction.hookaction) return false;",
|
||||||
|
" if (hookaction.round >= 3) useSkill(5001);",
|
||||||
|
" return true;",
|
||||||
|
"}",
|
||||||
|
].join("\\n"),
|
||||||
|
documentation: "Boss脚本模板:可读取回合/血量并调用 useSkill/switchPet。",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 后端代码
|
||||||
|
|
||||||
|
- 脚本执行器与函数绑定:`modules/config/model/boss_pet.go`
|
||||||
|
- AI 出手转发:`logic/service/fight/input/ai.go`
|
||||||
|
- BOSS 脚本注入:
|
||||||
|
- `logic/controller/fight_boss野怪和地图怪.go`
|
||||||
|
- `logic/controller/fight_塔.go`
|
||||||
|
|
||||||
@@ -4,30 +4,28 @@ import (
|
|||||||
"blazing/logic/service/fight/info"
|
"blazing/logic/service/fight/info"
|
||||||
"blazing/logic/service/player"
|
"blazing/logic/service/player"
|
||||||
configmodel "blazing/modules/config/model"
|
configmodel "blazing/modules/config/model"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/util/grand"
|
"github.com/gogf/gf/v2/util/grand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Shuffle 打乱切片顺序,使用 Fisher-Yates 洗牌算法,泛型支持任意类型
|
// Shuffle 打乱切片顺序,使用 Fisher-Yates 洗牌算法,泛型支持任意类型
|
||||||
func Shuffle[T any](slice []T) {
|
func Shuffle[T any](slice []T) {
|
||||||
// 从后往前遍历,逐个交换
|
|
||||||
for i := len(slice) - 1; i > 0; i-- {
|
for i := len(slice) - 1; i > 0; i-- {
|
||||||
// 生成 0 到 i 之间的随机索引
|
|
||||||
j := grand.Intn(i + 1)
|
j := grand.Intn(i + 1)
|
||||||
// 交换 i 和 j 位置的元素
|
|
||||||
slice[i], slice[j] = slice[j], slice[i]
|
slice[i], slice[j] = slice[j], slice[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (our *Input) GetAction() {
|
func (our *Input) GetAction() {
|
||||||
|
|
||||||
next := our.Exec(func(t Effect) bool {
|
next := our.Exec(func(t Effect) bool {
|
||||||
|
|
||||||
return t.HookAction()
|
return t.HookAction()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
scriptCtx := buildBossHookActionContext(our, next)
|
||||||
if aiPlayer, ok := our.Player.(*player.AI_player); ok && aiPlayer.BossScript != "" {
|
if aiPlayer, ok := our.Player.(*player.AI_player); ok && aiPlayer.BossScript != "" {
|
||||||
scriptBoss := &configmodel.BossConfig{Script: aiPlayer.BossScript}
|
scriptBoss := &configmodel.BossConfig{Script: aiPlayer.BossScript}
|
||||||
nextByScript, err := scriptBoss.RunHookActionScript(next)
|
nextByScript, err := scriptBoss.RunHookActionScript(scriptCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -37,28 +35,30 @@ func (our *Input) GetAction() {
|
|||||||
if !next {
|
if !next {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 获取己方当前宠物和对方当前宠物
|
|
||||||
|
if applyBossScriptAction(our, scriptCtx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
selfPet := our.FightC.GetCurrPET(our.Player)
|
selfPet := our.FightC.GetCurrPET(our.Player)
|
||||||
//没血就切换精灵
|
if selfPet == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if selfPet.Info.Hp <= 0 {
|
if selfPet.Info.Hp <= 0 {
|
||||||
for _, v := range our.AllPet {
|
for _, v := range our.AllPet {
|
||||||
if v.Info.Hp > 0 {
|
if v.Info.Hp > 0 {
|
||||||
// println("AI出手,切换宠物")
|
|
||||||
our.FightC.ChangePet(our.Player, v.Info.CatchTime)
|
our.FightC.ChangePet(our.Player, v.Info.CatchTime)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果没有可用宠物,则直接返回,不执行任何操作
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//oppPet := opp.FightC.GetCurrPET(opp.Player)
|
|
||||||
skills := selfPet.Skills
|
|
||||||
|
|
||||||
// 空技能列表直接返回,避免错误
|
skills := selfPet.Skills
|
||||||
if len(skills) == 0 {
|
if len(skills) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//println("AI出手,选择技能")
|
|
||||||
var usedskill *info.SkillEntity
|
var usedskill *info.SkillEntity
|
||||||
for _, s := range skills {
|
for _, s := range skills {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
@@ -67,30 +67,25 @@ func (our *Input) GetAction() {
|
|||||||
if !s.CanUse() {
|
if !s.CanUse() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 计算技能对对方的伤害(假设CalculatePower返回伤害值,或需从技能中获取)
|
|
||||||
s.DamageValue = our.CalculatePower(our.Opp, s)
|
s.DamageValue = our.CalculatePower(our.Opp, s)
|
||||||
|
|
||||||
oppPet := our.Opp.CurrentPet()
|
oppPet := our.Opp.CurrentPet()
|
||||||
if oppPet == nil {
|
if oppPet == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 判断是否能秒杀(伤害 >= 对方当前生命值)
|
if s.DamageValue.Cmp(oppPet.GetHP()) != -1 {
|
||||||
if s.DamageValue.Cmp(oppPet.GetHP()) != -1 { // 假设oppPet.HP为对方当前剩余生命值
|
|
||||||
|
|
||||||
if usedskill != nil {
|
if usedskill != nil {
|
||||||
if s.DamageValue.Cmp(usedskill.DamageValue) != -1 {
|
if s.DamageValue.Cmp(usedskill.DamageValue) != -1 {
|
||||||
usedskill = s
|
usedskill = s
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
usedskill = s
|
usedskill = s
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shuffle(skills)
|
Shuffle(skills)
|
||||||
if usedskill == nil {
|
if usedskill == nil {
|
||||||
|
|
||||||
for _, s := range skills {
|
for _, s := range skills {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
continue
|
continue
|
||||||
@@ -99,14 +94,87 @@ func (our *Input) GetAction() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
usedskill = s
|
usedskill = s
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if usedskill != nil {
|
if usedskill != nil {
|
||||||
our.FightC.UseSkill(our.Player, uint32(usedskill.XML.ID))
|
our.FightC.UseSkill(our.Player, uint32(usedskill.XML.ID))
|
||||||
} else {
|
} else {
|
||||||
our.FightC.UseSkill(our.Player, 0)
|
our.FightC.UseSkill(our.Player, 0)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBossHookActionContext(our *Input, hookAction bool) *configmodel.BossHookActionContext {
|
||||||
|
ctx := &configmodel.BossHookActionContext{
|
||||||
|
HookAction: hookAction,
|
||||||
|
Action: "auto",
|
||||||
|
}
|
||||||
|
ctx.UseSkillFn = func(skillID uint32) {
|
||||||
|
ctx.Action = "skill"
|
||||||
|
ctx.SkillID = skillID
|
||||||
|
}
|
||||||
|
ctx.SwitchPetFn = func(catchTime uint32) {
|
||||||
|
ctx.Action = "switch"
|
||||||
|
ctx.CatchTime = catchTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if our == nil || our.FightC == nil || our.Player == nil {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
overInfo := our.FightC.GetOverInfo()
|
||||||
|
ctx.Round = overInfo.Round
|
||||||
|
ctx.IsFirst = our.FightC.IsFirst(our.Player)
|
||||||
|
|
||||||
|
if selfPet := our.CurrentPet(); selfPet != nil {
|
||||||
|
ctx.Our = &configmodel.BossHookPetContext{
|
||||||
|
PetID: selfPet.Info.ID,
|
||||||
|
CatchTime: selfPet.Info.CatchTime,
|
||||||
|
Hp: selfPet.Info.Hp,
|
||||||
|
MaxHp: selfPet.Info.MaxHp,
|
||||||
|
}
|
||||||
|
ctx.Skills = make([]configmodel.BossHookSkillContext, 0, len(selfPet.Skills))
|
||||||
|
for _, s := range selfPet.Skills {
|
||||||
|
if s == nil || s.Info == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx.Skills = append(ctx.Skills, configmodel.BossHookSkillContext{
|
||||||
|
SkillID: s.Info.ID,
|
||||||
|
PP: s.Info.PP,
|
||||||
|
CanUse: s.CanUse(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if our.Opp != nil {
|
||||||
|
if oppPet := our.Opp.CurrentPet(); oppPet != nil {
|
||||||
|
ctx.Opp = &configmodel.BossHookPetContext{
|
||||||
|
PetID: oppPet.Info.ID,
|
||||||
|
CatchTime: oppPet.Info.CatchTime,
|
||||||
|
Hp: oppPet.Info.Hp,
|
||||||
|
MaxHp: oppPet.Info.MaxHp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyBossScriptAction(our *Input, ctx *configmodel.BossHookActionContext) bool {
|
||||||
|
if our == nil || ctx == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(strings.TrimSpace(ctx.Action)) {
|
||||||
|
case "", "auto":
|
||||||
|
return false
|
||||||
|
case "skill", "use_skill", "useskill":
|
||||||
|
our.FightC.UseSkill(our.Player, ctx.SkillID)
|
||||||
|
return true
|
||||||
|
case "switch", "change_pet", "changepet":
|
||||||
|
our.FightC.ChangePet(our.Player, ctx.CatchTime)
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,37 @@ type BossConfig struct {
|
|||||||
Rule []uint32 `gorm:"type:jsonb; ;comment:'战胜规则'" json:"rule"`
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"` // 我方技能
|
||||||
|
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:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
// TableName 指定BossConfig对应的数据库表名
|
// TableName 指定BossConfig对应的数据库表名
|
||||||
func (*BossConfig) TableName() string {
|
func (*BossConfig) TableName() string {
|
||||||
return TableNameBossConfig
|
return TableNameBossConfig
|
||||||
@@ -39,11 +70,8 @@ func (*BossConfig) GroupName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewBossConfig 创建一个新的BossConfig实例(初始化通用Model字段+所有默认值)
|
// NewBossConfig 创建一个新的BossConfig实例(初始化通用Model字段+所有默认值)
|
||||||
|
|
||||||
func NewBossConfig() *BossConfig {
|
func NewBossConfig() *BossConfig {
|
||||||
return &BossConfig{
|
return &BossConfig{Model: cool.NewModel()}
|
||||||
Model: cool.NewModel(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// init 程序启动时自动创建/同步boss_config表结构
|
// init 程序启动时自动创建/同步boss_config表结构
|
||||||
@@ -51,8 +79,7 @@ func init() {
|
|||||||
cool.CreateTable(&BossConfig{})
|
cool.CreateTable(&BossConfig{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunHookActionScript 执行BOSS脚本中的 hookAction,并传入 fight 的 hookaction 参数。
|
// RunHookActionScript 执行 BOSS 脚本 hookAction。
|
||||||
// 返回值遵循 HookAction 语义:true 允许继续出手,false 阻止继续出手。
|
|
||||||
func (b *BossConfig) RunHookActionScript(hookAction any) (bool, error) {
|
func (b *BossConfig) RunHookActionScript(hookAction any) (bool, error) {
|
||||||
if b == nil || strings.TrimSpace(b.Script) == "" {
|
if b == nil || strings.TrimSpace(b.Script) == "" {
|
||||||
return true, nil
|
return true, nil
|
||||||
@@ -64,6 +91,9 @@ func (b *BossConfig) RunHookActionScript(hookAction any) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vm := goja.New()
|
vm := goja.New()
|
||||||
|
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
|
||||||
|
bindBossScriptFunctions(vm, hookAction)
|
||||||
|
|
||||||
if _, err = vm.RunProgram(program); err != nil {
|
if _, err = vm.RunProgram(program); err != nil {
|
||||||
return false, fmt.Errorf("run boss script: %w", err)
|
return false, fmt.Errorf("run boss script: %w", err)
|
||||||
}
|
}
|
||||||
@@ -87,10 +117,49 @@ func (b *BossConfig) RunHookActionScript(hookAction any) (bool, error) {
|
|||||||
return false, fmt.Errorf("execute boss hookAction: %w", err)
|
return false, fmt.Errorf("execute boss hookAction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 与既有HookAction默认行为保持一致:未显式返回时视为允许继续出手。
|
|
||||||
if goja.IsUndefined(result) || goja.IsNull(result) {
|
if goja.IsUndefined(result) || goja.IsNull(result) {
|
||||||
return true, nil
|
return defaultHookActionResult(hookAction), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToBoolean(), 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,21 +2,17 @@ package model
|
|||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
type testHookAction struct {
|
|
||||||
Allow bool
|
|
||||||
Round int
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBossConfigRunHookActionScript(t *testing.T) {
|
func TestBossConfigRunHookActionScript(t *testing.T) {
|
||||||
boss := &BossConfig{
|
boss := &BossConfig{
|
||||||
Script: `
|
Script: `
|
||||||
function hookAction(hookaction) {
|
function hookAction(hookaction) {
|
||||||
return hookaction.Allow && hookaction.Round >= 2;
|
return hookaction.hookaction === true;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := boss.RunHookActionScript(testHookAction{Allow: true, Round: 2})
|
ctx := &BossHookActionContext{HookAction: true}
|
||||||
|
ok, err := boss.RunHookActionScript(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("RunHookActionScript returned error: %v", err)
|
t.Fatalf("RunHookActionScript returned error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -25,20 +21,67 @@ func TestBossConfigRunHookActionScript(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBossConfigRunHookActionScriptEmptyReturnDefaultsTrue(t *testing.T) {
|
func TestBossConfigRunHookActionScriptCallUseSkillFn(t *testing.T) {
|
||||||
boss := &BossConfig{
|
boss := &BossConfig{
|
||||||
Script: `
|
Script: `
|
||||||
function hookAction(hookaction) {
|
function hookAction(hookaction) {
|
||||||
var _ = hookaction;
|
if (hookaction.round >= 2) {
|
||||||
|
useSkill(5001);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := boss.RunHookActionScript(testHookAction{Allow: false, Round: 1})
|
ctx := &BossHookActionContext{
|
||||||
|
HookAction: true,
|
||||||
|
Round: 2,
|
||||||
|
Action: "auto",
|
||||||
|
}
|
||||||
|
ctx.UseSkillFn = func(skillID uint32) {
|
||||||
|
ctx.Action = "skill"
|
||||||
|
ctx.SkillID = skillID
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := boss.RunHookActionScript(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("RunHookActionScript returned error: %v", err)
|
t.Fatalf("RunHookActionScript returned error: %v", err)
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("RunHookActionScript = false, want true")
|
t.Fatalf("RunHookActionScript = false, want true")
|
||||||
}
|
}
|
||||||
|
if ctx.Action != "skill" || ctx.SkillID != 5001 {
|
||||||
|
t.Fatalf("useSkill not applied, got action=%q skill_id=%d", ctx.Action, ctx.SkillID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBossConfigRunHookActionScriptCallSwitchPetFn(t *testing.T) {
|
||||||
|
boss := &BossConfig{
|
||||||
|
Script: `
|
||||||
|
function hookAction(hookaction) {
|
||||||
|
switchPet(3);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := &BossHookActionContext{
|
||||||
|
HookAction: true,
|
||||||
|
Action: "auto",
|
||||||
|
}
|
||||||
|
ctx.SwitchPetFn = func(catchTime uint32) {
|
||||||
|
ctx.Action = "switch"
|
||||||
|
ctx.CatchTime = catchTime
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := boss.RunHookActionScript(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RunHookActionScript returned error: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("RunHookActionScript = false, want true")
|
||||||
|
}
|
||||||
|
if ctx.Action != "switch" || ctx.CatchTime != 3 {
|
||||||
|
t.Fatalf("switchPet not applied, got action=%q catch_time=%d", ctx.Action, ctx.CatchTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user