feat: 增强踢人逻辑与BOSS脚本支持
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
优化踢人超时处理和僵尸连接清理,支持BOSS动作脚本并增加测试,修复事件匹配与战斗循环中的并发问题。
This commit is contained in:
@@ -6,6 +6,16 @@ func AddClient(id uint32, client *ClientHandler) {
|
|||||||
Clientmap.Store(id, client) // sync.Map存值
|
Clientmap.Store(id, client) // sync.Map存值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清理指定client(uid=100000*onlineID+port)
|
||||||
|
func DeleteClientOnly(uid uint32) {
|
||||||
|
Clientmap.Delete(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理指定client(onlineID+port)
|
||||||
|
func DeleteClient(id, port uint32) {
|
||||||
|
Clientmap.Delete(100000*id + port)
|
||||||
|
}
|
||||||
|
|
||||||
// 取值示例
|
// 取值示例
|
||||||
func GetClient(id, port uint32) (*ClientHandler, bool) {
|
func GetClient(id, port uint32) (*ClientHandler, bool) {
|
||||||
// 普通map:client, ok := Clientmap[id]
|
// 普通map:client, ok := Clientmap[id]
|
||||||
|
|||||||
@@ -1,106 +1,149 @@
|
|||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/common/data/share"
|
"blazing/common/data/share"
|
||||||
"blazing/cool"
|
"blazing/cool"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
config "blazing/modules/config/service"
|
|
||||||
|
config "blazing/modules/config/service"
|
||||||
"github.com/filecoin-project/go-jsonrpc"
|
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/filecoin-project/go-jsonrpc"
|
||||||
)
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
// Define the server handler
|
|
||||||
type ServerHandler struct{}
|
// Define the server handler
|
||||||
|
type ServerHandler struct{}
|
||||||
// 实现踢人
|
|
||||||
func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
|
const kickForwardTimeout = 3 * time.Second
|
||||||
|
|
||||||
useid1, _ := share.ShareManager.GetUserOnline(userid)
|
// 实现踢人
|
||||||
if useid1 == 0 {
|
func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
|
||||||
return nil
|
useid1, err := share.ShareManager.GetUserOnline(userid)
|
||||||
}
|
if err != nil || useid1 == 0 {
|
||||||
|
// 请求到达时用户已离线,直接视为成功
|
||||||
cl, ok := cool.GetClientOnly(useid1)
|
return nil
|
||||||
if !ok {
|
}
|
||||||
return nil
|
|
||||||
}
|
cl, ok := cool.GetClientOnly(useid1)
|
||||||
cl.KickPerson(userid) //实现指定服务器踢人
|
if !ok || cl == nil {
|
||||||
return nil
|
// 目标服务器不在线,清理僵尸在线标记并视为成功
|
||||||
}
|
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||||
|
cool.DeleteClientOnly(useid1)
|
||||||
// 注册logic服务器
|
return nil
|
||||||
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error {
|
}
|
||||||
fmt.Println("注册logic服务器", id, port)
|
|
||||||
|
resultCh := make(chan error, 1)
|
||||||
//TODO 待修复滚动更新可能导致的玩家可以同时在旧服务器和新服务器同时在线的bug
|
go func() {
|
||||||
revClient, ok := jsonrpc.ExtractReverseClient[cool.ClientHandler](ctx)
|
resultCh <- cl.KickPerson(userid) // 实现指定服务器踢人
|
||||||
if !ok {
|
}()
|
||||||
return fmt.Errorf("no reverse client")
|
|
||||||
}
|
select {
|
||||||
t := config.NewServerService().GetServerID((id))
|
case callErr := <-resultCh:
|
||||||
|
if callErr == nil {
|
||||||
aa, ok := cool.GetClient(t.OnlineID, t.Port)
|
return nil
|
||||||
if ok && aa != nil { //如果已经存在且这个端口已经被存过
|
}
|
||||||
aa.QuitSelf(0)
|
|
||||||
}
|
// 调用失败后兜底:用户若已离线/切服/目标服不在线都算成功
|
||||||
cool.AddClient(100000*id+port, &revClient)
|
useid2, err2 := share.ShareManager.GetUserOnline(userid)
|
||||||
|
if err2 != nil || useid2 == 0 || useid2 != useid1 {
|
||||||
//Refurh()
|
return nil
|
||||||
return nil
|
}
|
||||||
|
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
|
||||||
}
|
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||||
|
cool.DeleteClientOnly(useid2)
|
||||||
func CServer() *jsonrpc.RPCServer {
|
return nil
|
||||||
// create a new server instance
|
}
|
||||||
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[cool.ClientHandler](""))
|
|
||||||
|
// 仍在线则返回失败,不按成功处理
|
||||||
rpcServer.Register("", &ServerHandler{})
|
return callErr
|
||||||
|
case <-time.After(kickForwardTimeout):
|
||||||
return rpcServer
|
// 仅防止无限等待;超时不算成功
|
||||||
|
useid2, err2 := share.ShareManager.GetUserOnline(userid)
|
||||||
}
|
if err2 != nil || useid2 == 0 || useid2 != useid1 {
|
||||||
|
return nil
|
||||||
var closer jsonrpc.ClientCloser
|
}
|
||||||
|
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
|
||||||
func StartClient(id, port uint32, callback any) *struct {
|
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||||
Kick func(uint32) error
|
cool.DeleteClientOnly(useid2)
|
||||||
|
return nil
|
||||||
RegisterLogic func(uint32, uint32) error
|
}
|
||||||
} {
|
|
||||||
//cool.Config.File.Domain = "127.0.0.1"
|
return fmt.Errorf("kick timeout, user still online: uid=%d server=%d", userid, useid2)
|
||||||
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
|
}
|
||||||
|
}
|
||||||
closer1, err := jsonrpc.NewMergeClient(context.Background(),
|
|
||||||
rpcaddr, "", []interface{}{
|
// 注册logic服务器
|
||||||
&RPCClient,
|
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error {
|
||||||
}, nil, jsonrpc.WithClientHandler("", callback),
|
fmt.Println("注册logic服务器", id, port)
|
||||||
jsonrpc.WithReconnFun(func() { RPCClient.RegisterLogic(id, port) }),
|
|
||||||
)
|
//TODO 待修复滚动更新可能导致的玩家可以同时在旧服务器和新服务器同时在线的bug
|
||||||
if err != nil {
|
revClient, ok := jsonrpc.ExtractReverseClient[cool.ClientHandler](ctx)
|
||||||
log.Fatalf("Failed to create client: %v", err)
|
if !ok {
|
||||||
}
|
return fmt.Errorf("no reverse client")
|
||||||
|
}
|
||||||
//if port != 0 { //注册logic
|
t := config.NewServerService().GetServerID((id))
|
||||||
defer RPCClient.RegisterLogic(id, port)
|
|
||||||
|
aa, ok := cool.GetClient(t.OnlineID, t.Port)
|
||||||
//}
|
if ok && aa != nil { //如果已经存在且这个端口已经被存过
|
||||||
|
aa.QuitSelf(0)
|
||||||
closer = closer1
|
}
|
||||||
|
cool.AddClient(100000*id+port, &revClient)
|
||||||
return &RPCClient
|
|
||||||
}
|
//Refurh()
|
||||||
|
return nil
|
||||||
// Setup RPCClient with reverse call handler
|
|
||||||
var RPCClient struct {
|
}
|
||||||
Kick func(uint32) error //踢人
|
|
||||||
|
func CServer() *jsonrpc.RPCServer {
|
||||||
RegisterLogic func(uint32, uint32) error
|
// create a new server instance
|
||||||
|
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[cool.ClientHandler](""))
|
||||||
// UserLogin func(int32, int32) error //用户登录事件
|
|
||||||
// UserLogout func(int32, int32) error //用户登出事件
|
rpcServer.Register("", &ServerHandler{})
|
||||||
}
|
|
||||||
|
return rpcServer
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var closer jsonrpc.ClientCloser
|
||||||
|
|
||||||
|
func StartClient(id, port uint32, callback any) *struct {
|
||||||
|
Kick func(uint32) error
|
||||||
|
|
||||||
|
RegisterLogic func(uint32, uint32) error
|
||||||
|
} {
|
||||||
|
//cool.Config.File.Domain = "127.0.0.1"
|
||||||
|
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
|
||||||
|
|
||||||
|
closer1, err := jsonrpc.NewMergeClient(context.Background(),
|
||||||
|
rpcaddr, "", []interface{}{
|
||||||
|
&RPCClient,
|
||||||
|
}, nil, jsonrpc.WithClientHandler("", callback),
|
||||||
|
jsonrpc.WithReconnFun(func() { RPCClient.RegisterLogic(id, port) }),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//if port != 0 { //注册logic
|
||||||
|
defer RPCClient.RegisterLogic(id, port)
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
closer = closer1
|
||||||
|
|
||||||
|
return &RPCClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup RPCClient with reverse call handler
|
||||||
|
var RPCClient struct {
|
||||||
|
Kick func(uint32) error //踢人
|
||||||
|
|
||||||
|
RegisterLogic func(uint32, uint32) error
|
||||||
|
|
||||||
|
// UserLogin func(int32, int32) error //用户登录事件
|
||||||
|
// UserLogout func(int32, int32) error //用户登出事件
|
||||||
|
}
|
||||||
|
|||||||
@@ -84,13 +84,11 @@ func (s *Server) OnClose(c gnet.Conn, err error) (action gnet.Action) {
|
|||||||
|
|
||||||
//logging.Infof("conn[%v] disconnected", c.RemoteAddr().String())
|
//logging.Infof("conn[%v] disconnected", c.RemoteAddr().String())
|
||||||
v, _ := c.Context().(*player.ClientData)
|
v, _ := c.Context().(*player.ClientData)
|
||||||
|
if v != nil {
|
||||||
v.LF.Close()
|
v.Close()
|
||||||
// v.LF.Close()
|
if v.Player != nil {
|
||||||
//close(v.MsgChan)
|
v.Player.Save() //保存玩家数据
|
||||||
if v.Player != nil {
|
}
|
||||||
v.Player.Save() //保存玩家数据
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Playe
|
|||||||
|
|
||||||
ai := player.NewAI_player(monsterInfo)
|
ai := player.NewAI_player(monsterInfo)
|
||||||
ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID)
|
ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID)
|
||||||
|
ai.BossScript = bossConfigs[0].Script
|
||||||
ai.Prop[0] = 2
|
ai.Prop[0] = 2
|
||||||
|
|
||||||
var fightC *fight.FightC
|
var fightC *fight.FightC
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func (h Controller) PetTawor(data *StartTwarInboundInfo, c *player.Player) (resu
|
|||||||
result = &fight.S2C_ChoiceLevelRequestInfo{CurFightLevel: currentLevel}
|
result = &fight.S2C_ChoiceLevelRequestInfo{CurFightLevel: currentLevel}
|
||||||
appendTowerNextBossPreview(&result.BossID, bossList)
|
appendTowerNextBossPreview(&result.BossID, bossList)
|
||||||
|
|
||||||
monsterInfo, ok := buildTowerMonsterInfo(currentBoss)
|
monsterInfo, bossScript, ok := buildTowerMonsterInfo(currentBoss)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||||
}
|
}
|
||||||
@@ -119,6 +119,7 @@ func (h Controller) PetTawor(data *StartTwarInboundInfo, c *player.Player) (resu
|
|||||||
c.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
c.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||||
|
|
||||||
ai := player.NewAI_player(monsterInfo)
|
ai := player.NewAI_player(monsterInfo)
|
||||||
|
ai.BossScript = bossScript
|
||||||
_, err = fight.NewFight(c, ai, c.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
_, err = fight.NewFight(c, ai, c.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||||
if foi.Reason != 0 || foi.WinnerId != c.Info.UserID {
|
if foi.Reason != 0 || foi.WinnerId != c.Info.UserID {
|
||||||
return
|
return
|
||||||
@@ -195,10 +196,10 @@ func appendTowerNextBossPreview(dst *[]uint32, bossList []configmodel.BaseTowerC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.PlayerInfo, bool) {
|
func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.PlayerInfo, string, bool) {
|
||||||
bosses := service.NewBossService().Get(towerBoss.BossIds[0])
|
bosses := service.NewBossService().Get(towerBoss.BossIds[0])
|
||||||
if len(bosses) == 0 {
|
if len(bosses) == 0 {
|
||||||
return nil, false
|
return nil, "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
monsterInfo := &model.PlayerInfo{Nick: towerBoss.Name}
|
monsterInfo := &model.PlayerInfo{Nick: towerBoss.Name}
|
||||||
@@ -234,7 +235,7 @@ func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.Player
|
|||||||
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
|
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
|
||||||
}
|
}
|
||||||
|
|
||||||
return monsterInfo, true
|
return monsterInfo, bosses[0].Script, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTowerFightWin(c *player.Player, cmd uint32, taskID int, currentLevel uint32) {
|
func handleTowerFightWin(c *player.Player, cmd uint32, taskID int, currentLevel uint32) {
|
||||||
|
|||||||
BIN
logic/fight.test
Executable file
BIN
logic/fight.test
Executable file
Binary file not shown.
@@ -2,6 +2,8 @@ package input
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/logic/service/fight/info"
|
"blazing/logic/service/fight/info"
|
||||||
|
"blazing/logic/service/player"
|
||||||
|
configmodel "blazing/modules/config/model"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/util/grand"
|
"github.com/gogf/gf/v2/util/grand"
|
||||||
)
|
)
|
||||||
@@ -22,6 +24,16 @@ func (our *Input) GetAction() {
|
|||||||
|
|
||||||
return t.HookAction()
|
return t.HookAction()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if aiPlayer, ok := our.Player.(*player.AI_player); ok && aiPlayer.BossScript != "" {
|
||||||
|
scriptBoss := &configmodel.BossConfig{Script: aiPlayer.BossScript}
|
||||||
|
nextByScript, err := scriptBoss.RunHookActionScript(next)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next = nextByScript
|
||||||
|
}
|
||||||
|
|
||||||
if !next {
|
if !next {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,8 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{})
|
|||||||
|
|
||||||
waitr := time.Duration(f.waittime)*time.Millisecond*10 + 30*time.Second
|
waitr := time.Duration(f.waittime)*time.Millisecond*10 + 30*time.Second
|
||||||
|
|
||||||
timeout := time.After(waitr)
|
timeout := time.NewTimer(waitr)
|
||||||
|
defer timeout.Stop()
|
||||||
for len(actions) < len(expectedSlots) {
|
for len(actions) < len(expectedSlots) {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -266,11 +267,12 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{})
|
|||||||
actions[key] = paction
|
actions[key] = paction
|
||||||
//fmt.Println("玩家执行动作:", pid, paction.Priority())
|
//fmt.Println("玩家执行动作:", pid, paction.Priority())
|
||||||
|
|
||||||
case <-timeout:
|
case <-timeout.C:
|
||||||
r := f.handleTimeout(expectedSlots, actions)
|
r := f.handleTimeout(expectedSlots, actions)
|
||||||
if r {
|
if r {
|
||||||
return flattenActionMap(actions)
|
return flattenActionMap(actions)
|
||||||
}
|
}
|
||||||
|
timeout.Reset(waitr)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +289,7 @@ func (f *FightC) handleTimeout(expectedSlots map[actionSlotKey]struct{}, actions
|
|||||||
}
|
}
|
||||||
player := f.getPlayerByID(key.PlayerID)
|
player := f.getPlayerByID(key.PlayerID)
|
||||||
if player != nil {
|
if player != nil {
|
||||||
go f.UseSkillAt(player, 0, key.ActorIndex, 0)
|
f.UseSkillAt(player, 0, key.ActorIndex, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package player
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/common/utils"
|
"blazing/common/utils"
|
||||||
"blazing/modules/config/model"
|
configmodel "blazing/modules/config/model"
|
||||||
|
playermodel "blazing/modules/player/model"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,15 +11,17 @@ import (
|
|||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Player) IsMatch(t model.Event) bool {
|
func (p *Player) IsMatch(t configmodel.Event) bool {
|
||||||
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
if len(t.Weather) > 0 {
|
||||||
return item == int32(p.GetSpace().MapBossSInfo.Wer)
|
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
||||||
})
|
return item == int32(p.GetSpace().MapBossSInfo.Wer)
|
||||||
if !ok {
|
})
|
||||||
// 不在同一天气下
|
if !ok {
|
||||||
return false
|
// 不在同一天气下
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.StartTime != "" && t.EndTime != "" {
|
if t.StartTime != "" && t.EndTime != "" {
|
||||||
ok, _ := utils.IsCurrentTimeInRange(t.StartTime, t.EndTime)
|
ok, _ := utils.IsCurrentTimeInRange(t.StartTime, t.EndTime)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -26,8 +29,74 @@ func (p *Player) IsMatch(t model.Event) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
if len(t.Week) > 0 {
|
||||||
|
week := int32(time.Now().Weekday())
|
||||||
|
if week == 0 {
|
||||||
|
week = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := lo.Find(t.Week, func(item int32) bool {
|
||||||
|
return item == week
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.Sprites) > 0 && !matchPetIDInList(t.Sprites, p.Info.PetList, p.Info.BackupPetList) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.FirstSprites) > 0 {
|
||||||
|
if len(p.Info.PetList) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
firstPetID := int32(p.Info.PetList[0].ID)
|
||||||
|
_, ok := lo.Find(t.FirstSprites, func(item int32) bool {
|
||||||
|
return item == firstPetID
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.MustTask) > 0 {
|
||||||
|
for _, taskID := range t.MustTask {
|
||||||
|
if p.Info.GetTask(int(taskID)) != playermodel.Completed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.MustItem) > 0 {
|
||||||
|
if p.Service == nil || p.Service.Item == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, itemID := range t.MustItem {
|
||||||
|
if p.Service.Item.CheakItem(uint32(itemID)) <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchPetIDInList(targetIDs []int32, petLists ...[]playermodel.PetInfo) bool {
|
||||||
|
for _, pets := range petLists {
|
||||||
|
for _, pet := range pets {
|
||||||
|
petID := int32(pet.ID)
|
||||||
|
_, ok := lo.Find(targetIDs, func(item int32) bool {
|
||||||
|
return item == petID
|
||||||
|
})
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应该根据怪物信息决定后端生成
|
// 应该根据怪物信息决定后端生成
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ type AI_player struct {
|
|||||||
baseplayer
|
baseplayer
|
||||||
|
|
||||||
CanCapture int
|
CanCapture int
|
||||||
|
BossScript string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
@@ -102,6 +103,10 @@ func putPacketData(buf []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ClientData) PushEvent(v []byte, submit func(task func()) error) {
|
func (h *ClientData) PushEvent(v []byte, submit func(task func()) error) {
|
||||||
|
if h == nil || h.IsClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var header common.TomeeHeader
|
var header common.TomeeHeader
|
||||||
header.Len = binary.BigEndian.Uint32(v[0:4])
|
header.Len = binary.BigEndian.Uint32(v[0:4])
|
||||||
header.CMD = binary.BigEndian.Uint32(v[5:9])
|
header.CMD = binary.BigEndian.Uint32(v[5:9])
|
||||||
@@ -111,9 +116,18 @@ func (h *ClientData) PushEvent(v []byte, submit func(task func()) error) {
|
|||||||
copy(header.Data, v[17:])
|
copy(header.Data, v[17:])
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = submit(func() {
|
err := submit(func() {
|
||||||
h.LF.Producer().Write(header)
|
if h.IsClosed() || h.LF == nil || !h.LF.Running() {
|
||||||
|
putPacketData(header.Data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.LF.Producer().Write(header); err != nil {
|
||||||
|
putPacketData(header.Data)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
putPacketData(header.Data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重写
|
// 重写
|
||||||
@@ -241,6 +255,20 @@ type ClientData struct {
|
|||||||
Wsmsg *WsCodec
|
Wsmsg *WsCodec
|
||||||
Conn gnet.Conn
|
Conn gnet.Conn
|
||||||
LF *lockfree.Lockfree[common.TomeeHeader]
|
LF *lockfree.Lockfree[common.TomeeHeader]
|
||||||
|
closed int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ClientData) IsClosed() bool {
|
||||||
|
return atomic.LoadInt32(&p.closed) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ClientData) Close() {
|
||||||
|
if !atomic.CompareAndSwapInt32(&p.closed, 0, 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.LF != nil && p.LF.Running() {
|
||||||
|
_ = p.LF.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ClientData) GetPlayer(userid uint32) *Player { //TODO 这里待优化,可能存在内存泄漏问题
|
func (p *ClientData) GetPlayer(userid uint32) *Player { //TODO 这里待优化,可能存在内存泄漏问题
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package player
|
package player
|
||||||
|
|
||||||
|
import "blazing/common/data/share"
|
||||||
|
|
||||||
func KickPlayer(userid uint32) error { //踢出玩家
|
func KickPlayer(userid uint32) error { //踢出玩家
|
||||||
//TODO 返回错误码
|
//TODO 返回错误码
|
||||||
//var player *entity.Player
|
//var player *entity.Player
|
||||||
if player1, ok := Mainplayer.Load(userid); ok {
|
if player1, ok := Mainplayer.Load(userid); ok {
|
||||||
player1.Player.Kick(false)
|
player1.Player.Kick(false)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//return player
|
// 已不在本服在线列表,视为离线并清理僵尸在线标记
|
||||||
|
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,14 +221,15 @@ func (ret *Space) init() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
func (p *Space) IsMatch(t model.Event) bool {
|
func (p *Space) IsMatch(t model.Event) bool {
|
||||||
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
if len(t.Weather) > 0 {
|
||||||
return item == int32(p.MapBossSInfo.Wer)
|
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
||||||
})
|
return item == int32(p.MapBossSInfo.Wer)
|
||||||
if !ok {
|
})
|
||||||
|
if !ok {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.StartTime != "" && t.EndTime != "" {
|
if t.StartTime != "" && t.EndTime != "" {
|
||||||
ok, _ := utils.IsCurrentTimeInRange(t.StartTime, t.EndTime)
|
ok, _ := utils.IsCurrentTimeInRange(t.StartTime, t.EndTime)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -236,6 +237,20 @@ func (p *Space) IsMatch(t model.Event) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(t.Week) > 0 {
|
||||||
|
week := int32(time.Now().Weekday())
|
||||||
|
if week == 0 {
|
||||||
|
week = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := lo.Find(t.Week, func(item int32) bool {
|
||||||
|
return item == week
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/cool"
|
"blazing/cool"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -46,3 +50,47 @@ func NewBossConfig() *BossConfig {
|
|||||||
func init() {
|
func init() {
|
||||||
cool.CreateTable(&BossConfig{})
|
cool.CreateTable(&BossConfig{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunHookActionScript 执行BOSS脚本中的 hookAction,并传入 fight 的 hookaction 参数。
|
||||||
|
// 返回值遵循 HookAction 语义:true 允许继续出手,false 阻止继续出手。
|
||||||
|
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()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与既有HookAction默认行为保持一致:未显式返回时视为允许继续出手。
|
||||||
|
if goja.IsUndefined(result) || goja.IsNull(result) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToBoolean(), nil
|
||||||
|
}
|
||||||
|
|||||||
44
modules/config/model/boss_pet_test.go
Normal file
44
modules/config/model/boss_pet_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
type testHookAction struct {
|
||||||
|
Allow bool
|
||||||
|
Round int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBossConfigRunHookActionScript(t *testing.T) {
|
||||||
|
boss := &BossConfig{
|
||||||
|
Script: `
|
||||||
|
function hookAction(hookaction) {
|
||||||
|
return hookaction.Allow && hookaction.Round >= 2;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := boss.RunHookActionScript(testHookAction{Allow: true, Round: 2})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RunHookActionScript returned error: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("RunHookActionScript = false, want true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBossConfigRunHookActionScriptEmptyReturnDefaultsTrue(t *testing.T) {
|
||||||
|
boss := &BossConfig{
|
||||||
|
Script: `
|
||||||
|
function hookAction(hookaction) {
|
||||||
|
var _ = hookaction;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := boss.RunHookActionScript(testHookAction{Allow: false, Round: 1})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RunHookActionScript returned error: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("RunHookActionScript = false, want true")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -179,19 +179,51 @@ func (s *InfoService) Gensession() string {
|
|||||||
func (s *InfoService) Kick(id uint32) error {
|
func (s *InfoService) Kick(id uint32) error {
|
||||||
|
|
||||||
useid1, err := share.ShareManager.GetUserOnline(id)
|
useid1, err := share.ShareManager.GetUserOnline(id)
|
||||||
|
if err != nil || useid1 == 0 {
|
||||||
if err != nil {
|
// 请求进入时已经离线,视为成功
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, ok := cool.GetClientOnly(useid1)
|
cl, ok := cool.GetClientOnly(useid1)
|
||||||
if ok {
|
if !ok || cl == nil {
|
||||||
err := cl.KickPerson(id) //实现指定服务器踢人
|
// 目标服务器不在线,清理僵尸在线标记并视为成功
|
||||||
if err != nil {
|
_ = share.ShareManager.DeleteUserOnline(id)
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resultCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
resultCh <- cl.KickPerson(id) // 实现指定服务器踢人
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case callErr := <-resultCh:
|
||||||
|
if callErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用失败后兜底:若已离线/切服/目标服不在线则视为成功
|
||||||
|
useid2, err2 := share.ShareManager.GetUserOnline(id)
|
||||||
|
if err2 != nil || useid2 == 0 || useid2 != useid1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
|
||||||
|
_ = share.ShareManager.DeleteUserOnline(id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return callErr
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
// 防止异常场景下无限等待;超时不按成功处理
|
||||||
|
useid2, err2 := share.ShareManager.GetUserOnline(id)
|
||||||
|
if err2 != nil || useid2 == 0 || useid2 != useid1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
|
||||||
|
_ = share.ShareManager.DeleteUserOnline(id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("kick timeout, user still online: uid=%d server=%d", id, useid2)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveToLocalFile 兜底保存:将数据写入本地lose文件夹
|
// saveToLocalFile 兜底保存:将数据写入本地lose文件夹
|
||||||
|
|||||||
Reference in New Issue
Block a user