feat(fight): 添加旧组队协议支持并优化战斗系统 - 实现了旧组队协议相关功能,包括GroupReadyFightFinish、GroupUseSkill、 GroupUseItem、GroupChangePet和GroupEscape方法 - 新增组队战斗相关的入站信息结构体定义 - 实现了组队BOSS战斗逻辑,添加groupBossSlotLimit常量 - 重构宠物技能设置逻辑,调整金币消耗时机 - 优化战斗循环逻辑,添加对无行动槽位的处理 - 改进AI行动逻辑,增加多位置目标选择
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -29,7 +29,7 @@
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["-id=2"],
|
||||
"args": ["-id=99"],
|
||||
|
||||
"program": "${workspaceFolder}/logic"
|
||||
}
|
||||
|
||||
@@ -25,6 +25,54 @@ func (h Controller) OnReadyToFight(data *ReadyToFightInboundInfo, c *player.Play
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// GroupReadyFightFinish 旧组队协议准备完成。
|
||||
func (h Controller) GroupReadyFightFinish(data *GroupReadyFightFinishInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
go c.FightC.ReadyFight(c)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupUseSkill(data *GroupUseSkillInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
targetRelation := fight.SkillTargetOpponent
|
||||
if data.TargetSide == 1 {
|
||||
targetRelation = fight.SkillTargetAlly
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewSkillActionEnvelope(data.SkillId, int(data.ActorIndex), int(data.TargetPos), targetRelation, 0))
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (h Controller) GroupUseItem(data *GroupUseItemInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewItemActionEnvelope(0, data.ItemId, int(data.ActorIndex), int(data.ActorIndex), fight.SkillTargetSelf))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupChangePet(data *GroupChangePetInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewChangeActionEnvelope(data.CatchTime, int(data.ActorIndex)))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupEscape(data *GroupEscapeInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if fightC, ok := c.FightC.(*fight.FightC); ok && fightC != nil && fightC.LegacyGroupProtocol {
|
||||
fightC.SendLegacyEscapeSuccess(c, int(data.ActorIndex))
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewEscapeActionEnvelope())
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// UseSkill 使用技能包
|
||||
func (h Controller) UseSkill(data *UseSkillInInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
rewardItemExpPool = 3
|
||||
rewardItemExpPool = 3
|
||||
groupBossSlotLimit = 3
|
||||
)
|
||||
|
||||
// PlayerFightBoss 挑战地图boss
|
||||
@@ -50,7 +51,7 @@ func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Playe
|
||||
ai.AddBattleProp(0, 2)
|
||||
|
||||
var fightC *fight.FightC
|
||||
fightC, err = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||
fightC, err = startMapBossFight(mapNode, p, ai, func(foi model.FightOverInfo) {
|
||||
if mapNode.WinBonusID == 0 {
|
||||
return
|
||||
}
|
||||
@@ -65,6 +66,63 @@ func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Playe
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func startMapBossFight(
|
||||
mapNode *configmodel.MapNode,
|
||||
p *player.Player,
|
||||
ai *player.AI_player,
|
||||
fn func(model.FightOverInfo),
|
||||
) (*fight.FightC, errorcode.ErrorCode) {
|
||||
ourPets := p.GetPetInfo(100)
|
||||
oppPets := ai.GetPetInfo(0)
|
||||
if mapNode != nil && mapNode.IsGroupBoss != 0 {
|
||||
ourSlots := buildGroupBossPetSlots(ourPets, groupBossSlotLimit)
|
||||
oppSlots := buildGroupBossPetSlots(oppPets, groupBossSlotLimit)
|
||||
if len(ourSlots) > 0 && len(oppSlots) > 0 {
|
||||
return fight.NewLegacyGroupFightSingleControllerN(p, ai, ourSlots, oppSlots, fn)
|
||||
}
|
||||
}
|
||||
return fight.NewFight(p, ai, ourPets, oppPets, fn)
|
||||
}
|
||||
|
||||
func buildGroupBossPetSlots(pets []model.PetInfo, slotLimit int) [][]model.PetInfo {
|
||||
if len(pets) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
slots := make([][]model.PetInfo, 0, slotLimit)
|
||||
for _, pet := range pets {
|
||||
if pet.Hp == 0 {
|
||||
continue
|
||||
}
|
||||
if slotLimit <= 0 {
|
||||
slotLimit = 3
|
||||
}
|
||||
if len(slots) < slotLimit {
|
||||
slots = append(slots, []model.PetInfo{pet})
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(slots) == 0 {
|
||||
return nil
|
||||
}
|
||||
var idx int = 0
|
||||
for _, pet := range pets[len(slots):] {
|
||||
if pet.Hp == 0 {
|
||||
continue
|
||||
}
|
||||
for step := 0; step < len(slots); step++ {
|
||||
slotIdx := (idx + step) % len(slots)
|
||||
if len(slots[slotIdx]) < 6 {
|
||||
slots[slotIdx] = append(slots[slotIdx], pet)
|
||||
idx = (slotIdx + 1) % len(slots)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return slots
|
||||
}
|
||||
|
||||
// OnPlayerFightNpcMonster 战斗野怪
|
||||
func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err = p.CanFight(); err != 0 {
|
||||
|
||||
@@ -19,6 +19,36 @@ type ReadyToFightInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2404" struc:"skip"`
|
||||
}
|
||||
|
||||
// GroupReadyFightFinishInboundInfo 旧组队协议准备完成。
|
||||
type GroupReadyFightFinishInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7556" struc:"skip"`
|
||||
}
|
||||
|
||||
type GroupUseSkillInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7558" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
TargetSide uint8
|
||||
TargetPos uint8
|
||||
SkillId uint32
|
||||
}
|
||||
|
||||
type GroupUseItemInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7562" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
ItemId uint32
|
||||
}
|
||||
|
||||
type GroupChangePetInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7563" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
CatchTime uint32
|
||||
}
|
||||
|
||||
type GroupEscapeInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7565" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
}
|
||||
|
||||
// EscapeFightInboundInfo 定义请求或响应数据结构。
|
||||
type EscapeFightInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2410" struc:"skip"`
|
||||
|
||||
@@ -69,12 +69,6 @@ func (h Controller) GetPetLearnableSkills(
|
||||
func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result *pet.ChangeSkillOutInfo, err errorcode.ErrorCode) {
|
||||
const setSkillCost = 50
|
||||
|
||||
if !c.GetCoins(setSkillCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
c.Info.Coins -= setSkillCost
|
||||
|
||||
_, currentPet, ok := c.FindPet(data.CatchTime)
|
||||
if !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
@@ -93,28 +87,39 @@ func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
_, _, ok = utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool { //已经存在技能
|
||||
_, _, ok = utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == data.ReplaceSkill
|
||||
})
|
||||
if ok {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
maxPP := uint32(skillInfo.MaxPP)
|
||||
if data.HasSkill == 0 && len(currentPet.SkillList) >= 4 {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
if data.HasSkill != 0 {
|
||||
// 查找要学习的技能并替换
|
||||
_, targetSkill, found := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
_, _, found := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == data.HasSkill
|
||||
})
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
}
|
||||
|
||||
if !c.GetCoins(setSkillCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
c.Info.Coins -= setSkillCost
|
||||
maxPP := uint32(skillInfo.MaxPP)
|
||||
if data.HasSkill != 0 {
|
||||
_, targetSkill, _ := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == data.HasSkill
|
||||
})
|
||||
targetSkill.ID = data.ReplaceSkill
|
||||
targetSkill.PP = maxPP
|
||||
} else {
|
||||
if len(currentPet.SkillList) >= 4 {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
currentPet.SkillList = append(currentPet.SkillList, model.SkillInfo{
|
||||
ID: data.ReplaceSkill,
|
||||
PP: maxPP,
|
||||
@@ -130,12 +135,6 @@ func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result
|
||||
func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
const skillSortCost = 50
|
||||
|
||||
if !c.GetCoins(skillSortCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
c.Info.Coins -= skillSortCost
|
||||
|
||||
_, currentPet, ok := c.FindPet(data.CapTm)
|
||||
if !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
@@ -175,6 +174,12 @@ func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (resul
|
||||
if len(newSkillList) > 4 {
|
||||
newSkillList = newSkillList[:4]
|
||||
}
|
||||
|
||||
if !c.GetCoins(skillSortCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
c.Info.Coins -= skillSortCost
|
||||
currentPet.SkillList = newSkillList
|
||||
|
||||
return nil, 0
|
||||
|
||||
@@ -147,6 +147,9 @@ func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
|
||||
if f.GetInputByPlayer(c, true) != nil {
|
||||
f.WinnerId = f.GetInputByPlayer(c, true).UserID
|
||||
}
|
||||
f.FightOverInfo.Reason = f.Reason
|
||||
f.FightOverInfo.WinnerId = f.WinnerId
|
||||
f.closefight = true
|
||||
|
||||
close(f.quit)
|
||||
|
||||
@@ -281,6 +284,17 @@ func (f *FightC) UseItemAt(c common.PlayerI, cacthid, itemid uint32, actorIndex,
|
||||
|
||||
// ReadyFight 处理玩家战斗准备逻辑,当满足条件时启动战斗循环
|
||||
func (f *FightC) ReadyFight(c common.PlayerI) {
|
||||
if f.LegacyGroupProtocol {
|
||||
input := f.GetInputByPlayer(c, false)
|
||||
if input == nil {
|
||||
return
|
||||
}
|
||||
input.Finished = true
|
||||
if f.checkBothPlayersReady(c) {
|
||||
f.startLegacyGroupBattle()
|
||||
}
|
||||
return
|
||||
}
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
|
||||
ff.Player.SendPackCmd(2404, &info.S2C_2404{UserID: c.GetInfo().UserID})
|
||||
@@ -355,8 +369,23 @@ func (f *FightC) startBattle(startInfo info.FightStartOutboundInfo) {
|
||||
go f.battleLoop()
|
||||
|
||||
// 向双方广播战斗开始信息
|
||||
if f.LegacyGroupProtocol {
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
f.sendLegacyGroupStart(ff.Player)
|
||||
})
|
||||
} else {
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
ff.Player.SendPackCmd(2504, &startInfo)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (f *FightC) startLegacyGroupBattle() {
|
||||
f.startl.Do(func() {
|
||||
go f.battleLoop()
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
ff.Player.SendPackCmd(2504, &startInfo)
|
||||
f.sendLegacyGroupStart(ff.Player)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ type NewSel41 struct {
|
||||
NewSel0
|
||||
}
|
||||
|
||||
func (e *NewSel41) Skill_Use_ex() bool {
|
||||
func (e *NewSel41) Skill_Use() bool {
|
||||
if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -416,6 +416,10 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
||||
|
||||
}
|
||||
|
||||
if f.LegacyGroupProtocol {
|
||||
f.sendLegacyRoundBroadcast(firstAttack, secondAttack)
|
||||
}
|
||||
|
||||
attackValueResult := f.buildNoteUseSkillOutboundInfo()
|
||||
//因为切完才能广播,所以必须和回合结束分开结算
|
||||
f.Broadcast(func(fighter *input.Input) {
|
||||
@@ -446,6 +450,17 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
||||
|
||||
}
|
||||
func (f *FightC) TURNOVER(cur *input.Input) {
|
||||
var _hasBackup bool
|
||||
if cur == nil {
|
||||
return
|
||||
}
|
||||
for _, pet := range cur.BenchPets() {
|
||||
if pet != nil && pet.Info.Hp > 0 {
|
||||
_hasBackup = true
|
||||
break
|
||||
}
|
||||
}
|
||||
f.sendLegacySpriteDie(cur, _hasBackup)
|
||||
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
|
||||
@@ -459,6 +474,9 @@ func (f *FightC) TURNOVER(cur *input.Input) {
|
||||
if f.IsWin(f.GetInputByPlayer(cur.Player, true)) { //然后检查是否战斗结束
|
||||
|
||||
f.FightOverInfo.WinnerId = f.GetInputByPlayer(cur.Player, true).UserID
|
||||
f.FightOverInfo.Reason = model.BattleOverReason.DefaultEnd
|
||||
f.WinnerId = f.FightOverInfo.WinnerId
|
||||
f.Reason = model.BattleOverReason.DefaultEnd
|
||||
|
||||
f.closefight = true
|
||||
// break
|
||||
|
||||
428
logic/service/fight/group_legacy.go
Normal file
428
logic/service/fight/group_legacy.go
Normal file
@@ -0,0 +1,428 @@
|
||||
package fight
|
||||
|
||||
import (
|
||||
"blazing/logic/service/common"
|
||||
"blazing/logic/service/fight/action"
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/modules/player/model"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
groupCmdReadyToFight uint32 = 7555
|
||||
groupCmdReadyFightFinish uint32 = 7556
|
||||
groupCmdStartFight uint32 = 7557
|
||||
groupCmdUseSkill uint32 = 7558
|
||||
groupCmdSkillHurt uint32 = 7559
|
||||
groupCmdFightOver uint32 = 7560
|
||||
groupCmdSpriteDie uint32 = 7561
|
||||
groupCmdUseItem uint32 = 7562
|
||||
groupCmdChangePet uint32 = 7563
|
||||
groupCmdEscape uint32 = 7565
|
||||
groupCmdBoutDone uint32 = 7566
|
||||
groupCmdChangePetSuc uint32 = 7567
|
||||
groupCmdEscapeSuc uint32 = 7568
|
||||
groupCmdChat uint32 = 7569
|
||||
groupCmdLoadPercent uint32 = 7571
|
||||
groupCmdLoadPercentNotice uint32 = 7572
|
||||
groupCmdSpriteNotice uint32 = 7573
|
||||
groupCmdFightWinClose uint32 = 7574
|
||||
groupCmdFightOvertime uint32 = 7585
|
||||
groupCmdSkillPlayOver uint32 = 7586
|
||||
groupCmdFightTimeoutExit uint32 = 7587
|
||||
groupCmdFightRelation uint32 = 7588
|
||||
groupModelNPC uint32 = 3
|
||||
groupModelBoss uint32 = 4
|
||||
groupModelPlayerSingle uint32 = 5
|
||||
groupModelPlayerMulti uint32 = 6
|
||||
)
|
||||
|
||||
func groupModelByFight(f *FightC) uint32 {
|
||||
if f == nil {
|
||||
return groupModelBoss
|
||||
}
|
||||
switch {
|
||||
case f.Info.Status == 0:
|
||||
return groupModelNPC
|
||||
case f.Info.Status == 1 && f.Info.Mode == 1:
|
||||
return groupModelPlayerSingle
|
||||
case f.Info.Status == 1:
|
||||
return groupModelPlayerMulti
|
||||
default:
|
||||
return groupModelBoss
|
||||
}
|
||||
}
|
||||
|
||||
func writeUint8(buf *bytes.Buffer, v uint8) {
|
||||
_ = buf.WriteByte(v)
|
||||
}
|
||||
|
||||
func writeUint32(buf *bytes.Buffer, v uint32) {
|
||||
_ = binary.Write(buf, binary.BigEndian, v)
|
||||
}
|
||||
|
||||
func writeInt32(buf *bytes.Buffer, v int32) {
|
||||
_ = binary.Write(buf, binary.BigEndian, v)
|
||||
}
|
||||
|
||||
func writeFixedString16(buf *bytes.Buffer, s string) {
|
||||
raw := make([]byte, 16)
|
||||
copy(raw, []byte(s))
|
||||
_, _ = buf.Write(raw)
|
||||
}
|
||||
|
||||
func buildPacket(cmd uint32, userID uint32, payload []byte) []byte {
|
||||
header := common.NewTomeeHeader(cmd, userID)
|
||||
totalLen := uint32(17 + len(payload))
|
||||
header.Len = totalLen
|
||||
buf := make([]byte, totalLen)
|
||||
binary.BigEndian.PutUint32(buf[0:4], totalLen)
|
||||
buf[4] = header.Version
|
||||
binary.BigEndian.PutUint32(buf[5:9], cmd)
|
||||
binary.BigEndian.PutUint32(buf[9:13], userID)
|
||||
binary.BigEndian.PutUint32(buf[13:17], 0)
|
||||
copy(buf[17:], payload)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacyGroupReady() {
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
if ff == nil || ff.Player == nil {
|
||||
return
|
||||
}
|
||||
sendLegacyPacket(ff.Player, groupCmdReadyToFight, f.buildLegacyGroupReadyPayload())
|
||||
})
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacyGroupStart(player common.PlayerI) {
|
||||
if player == nil {
|
||||
return
|
||||
}
|
||||
sendLegacyPacket(player, groupCmdStartFight, f.buildLegacyGroupStartPayload())
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacyGroupOver(player common.PlayerI, over *model.FightOverInfo) {
|
||||
if player == nil {
|
||||
return
|
||||
}
|
||||
sendLegacyPacket(player, groupCmdFightOver, f.buildLegacyGroupOverPayload(over))
|
||||
}
|
||||
|
||||
func sendLegacyPacket(player common.PlayerI, cmd uint32, payload []byte) {
|
||||
if player == nil {
|
||||
return
|
||||
}
|
||||
if sender, ok := player.(interface{ SendPack([]byte) error }); ok {
|
||||
_ = sender.SendPack(buildPacket(cmd, player.GetInfo().UserID, payload))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FightC) buildLegacyGroupReadyPayload() []byte {
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
users [][]*input.Input
|
||||
)
|
||||
|
||||
writeUint32(&buf, groupModelByFight(f))
|
||||
users = [][]*input.Input{f.Our, f.Opp}
|
||||
for sideIndex, sideInputs := range users {
|
||||
writeUint8(&buf, 1)
|
||||
var leaderID uint32
|
||||
if len(sideInputs) > 0 && sideInputs[0] != nil && sideInputs[0].Player != nil {
|
||||
leaderID = sideInputs[0].Player.GetInfo().UserID
|
||||
}
|
||||
writeUint32(&buf, leaderID)
|
||||
writeUint8(&buf, 1)
|
||||
if leaderID == 0 {
|
||||
writeUint32(&buf, 0)
|
||||
writeFixedString16(&buf, "boss")
|
||||
} else {
|
||||
writeUint32(&buf, leaderID)
|
||||
writeFixedString16(&buf, sideInputs[0].Player.GetInfo().Nick)
|
||||
}
|
||||
writeUint32(&buf, uint32(len(sideInputs)))
|
||||
for _, slot := range sideInputs {
|
||||
if slot == nil || slot.CurrentPet() == nil {
|
||||
continue
|
||||
}
|
||||
currentPet := slot.CurrentPet()
|
||||
writeUint32(&buf, currentPet.Info.ID)
|
||||
writeUint32(&buf, uint32(len(currentPet.Info.SkillList)))
|
||||
for _, skill := range currentPet.Info.SkillList {
|
||||
writeUint32(&buf, skill.ID)
|
||||
}
|
||||
}
|
||||
if sideIndex == 0 && len(sideInputs) == 0 {
|
||||
writeUint32(&buf, 0)
|
||||
writeFixedString16(&buf, "")
|
||||
writeUint32(&buf, 0)
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (f *FightC) buildLegacyGroupStartPayload() []byte {
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
sides [][]*input.Input
|
||||
)
|
||||
|
||||
writeUint8(&buf, 0)
|
||||
sides = [][]*input.Input{f.Our, f.Opp}
|
||||
for sideIndex, sideInputs := range sides {
|
||||
writeUint8(&buf, uint8(len(sideInputs)))
|
||||
for pos, slot := range sideInputs {
|
||||
if slot == nil || slot.CurrentPet() == nil {
|
||||
continue
|
||||
}
|
||||
currentPet := slot.CurrentPet()
|
||||
writeUint8(&buf, uint8(sideIndex+1))
|
||||
writeUint8(&buf, uint8(pos))
|
||||
if slot.Player != nil {
|
||||
writeUint32(&buf, slot.Player.GetInfo().UserID)
|
||||
} else {
|
||||
writeUint32(&buf, 0)
|
||||
}
|
||||
writeUint8(&buf, 0)
|
||||
writeUint32(&buf, currentPet.Info.ID)
|
||||
writeUint32(&buf, currentPet.Info.CatchTime)
|
||||
writeUint32(&buf, currentPet.Info.Hp)
|
||||
writeUint32(&buf, currentPet.Info.MaxHp)
|
||||
writeUint32(&buf, currentPet.Info.Level)
|
||||
writeUint32(&buf, 0)
|
||||
writeUint32(&buf, 1)
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (f *FightC) buildLegacyGroupOverPayload(over *model.FightOverInfo) []byte {
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
winnerID uint32
|
||||
endReason uint32
|
||||
playerInfo *model.PlayerInfo
|
||||
)
|
||||
if over != nil {
|
||||
winnerID = over.WinnerId
|
||||
endReason = uint32(over.Reason)
|
||||
}
|
||||
if our := f.primaryOurPlayer(); our != nil {
|
||||
playerInfo = our.GetInfo()
|
||||
}
|
||||
writeUint8(&buf, 0)
|
||||
writeUint32(&buf, endReason)
|
||||
writeUint32(&buf, winnerID)
|
||||
writeUint32(&buf, 0)
|
||||
if playerInfo != nil {
|
||||
writeUint32(&buf, uint32(playerInfo.TwoTimes))
|
||||
writeUint32(&buf, uint32(playerInfo.ThreeTimes))
|
||||
writeUint32(&buf, playerInfo.AutoFightTime)
|
||||
writeUint32(&buf, 0)
|
||||
writeUint32(&buf, uint32(playerInfo.EnergyTime))
|
||||
writeUint32(&buf, playerInfo.LearnTimes)
|
||||
} else {
|
||||
for i := 0; i < 6; i++ {
|
||||
writeUint32(&buf, 0)
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (f *FightC) SendLegacyEscapeSuccess(player common.PlayerI, actorIndex int) {
|
||||
if f == nil || !f.LegacyGroupProtocol || player == nil {
|
||||
return
|
||||
}
|
||||
payload := f.buildLegacyEscapePayload(player, actorIndex)
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
if ff == nil || ff.Player == nil {
|
||||
return
|
||||
}
|
||||
sendLegacyPacket(ff.Player, groupCmdEscapeSuc, payload)
|
||||
})
|
||||
}
|
||||
|
||||
func (f *FightC) buildLegacyEscapePayload(player common.PlayerI, actorIndex int) []byte {
|
||||
var buf bytes.Buffer
|
||||
side := uint8(1)
|
||||
if !f.isOurPlayerID(player.GetInfo().UserID) {
|
||||
side = 2
|
||||
}
|
||||
writeUint32(&buf, player.GetInfo().UserID)
|
||||
writeFixedString16(&buf, player.GetInfo().Nick)
|
||||
writeUint8(&buf, side)
|
||||
writeUint8(&buf, uint8(actorIndex))
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacyRoundBroadcast(firstAttack, secondAttack *action.SelectSkillAction) {
|
||||
if f == nil || !f.LegacyGroupProtocol {
|
||||
return
|
||||
}
|
||||
if firstAttack != nil {
|
||||
f.sendLegacyGroupSkillHurt(firstAttack)
|
||||
}
|
||||
if secondAttack != nil {
|
||||
f.sendLegacyGroupSkillHurt(secondAttack)
|
||||
}
|
||||
f.sendLegacyGroupBoutDone()
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacyGroupSkillHurt(skillAction *action.SelectSkillAction) {
|
||||
var payload []byte
|
||||
if skillAction == nil {
|
||||
return
|
||||
}
|
||||
payload = f.buildLegacyGroupSkillHurtPayload(skillAction)
|
||||
if len(payload) == 0 {
|
||||
return
|
||||
}
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
if ff == nil || ff.Player == nil {
|
||||
return
|
||||
}
|
||||
sendLegacyPacket(ff.Player, groupCmdSkillHurt, payload)
|
||||
})
|
||||
}
|
||||
|
||||
func (f *FightC) buildLegacyGroupSkillHurtPayload(skillAction *action.SelectSkillAction) []byte {
|
||||
var buf bytes.Buffer
|
||||
attacker := f.GetInputByAction(skillAction, false)
|
||||
defender := f.GetInputByAction(skillAction, true)
|
||||
if attacker == nil || defender == nil || attacker.AttackValue == nil || defender.AttackValue == nil {
|
||||
return nil
|
||||
}
|
||||
writeUint8(&buf, 0)
|
||||
f.writeLegacySkillHurtInfo(&buf, skillAction, attacker, defender, true)
|
||||
f.writeLegacySkillHurtInfo(&buf, skillAction, defender, attacker, false)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (f *FightC) writeLegacySkillHurtInfo(buf *bytes.Buffer, skillAction *action.SelectSkillAction, self *input.Input, opponent *input.Input, isAttacker bool) {
|
||||
var (
|
||||
moveID uint32
|
||||
attackVal *model.AttackValue
|
||||
currentPet *info.BattlePetEntity
|
||||
side uint8
|
||||
pos uint8
|
||||
)
|
||||
if self == nil || buf == nil {
|
||||
return
|
||||
}
|
||||
if self.AttackValue == nil {
|
||||
attackVal = info.NewAttackValue(self.UserID)
|
||||
} else {
|
||||
attackVal = self.AttackValue
|
||||
}
|
||||
currentPet = self.CurrentPet()
|
||||
side = 1
|
||||
if !f.isOurPlayerID(self.UserID) {
|
||||
side = 2
|
||||
}
|
||||
pos = uint8(self.TeamSlotIndex())
|
||||
moveID = attackVal.SkillID
|
||||
if isAttacker {
|
||||
if skillAction != nil && skillAction.SkillEntity != nil {
|
||||
moveID = uint32(skillAction.SkillEntity.XML.ID)
|
||||
}
|
||||
writeUint8(buf, 0)
|
||||
} else {
|
||||
writeUint8(buf, 1)
|
||||
moveID = 0
|
||||
}
|
||||
writeUint8(buf, side)
|
||||
writeUint8(buf, pos)
|
||||
writeUint32(buf, self.UserID)
|
||||
for i := 0; i < 20; i++ {
|
||||
writeUint8(buf, uint8(attackVal.Status[i]))
|
||||
}
|
||||
writeUint8(buf, 0)
|
||||
writeUint8(buf, 0)
|
||||
for i := 0; i < 6; i++ {
|
||||
writeUint8(buf, uint8(attackVal.Prop[i]))
|
||||
}
|
||||
if currentPet != nil {
|
||||
writeUint32(buf, currentPet.Info.ID)
|
||||
} else {
|
||||
writeUint32(buf, 0)
|
||||
}
|
||||
writeUint32(buf, moveID)
|
||||
if currentPet != nil {
|
||||
writeUint32(buf, currentPet.Info.Hp)
|
||||
writeUint32(buf, currentPet.Info.MaxHp)
|
||||
writeUint32(buf, uint32(len(currentPet.Info.SkillList)))
|
||||
for _, skill := range currentPet.Info.SkillList {
|
||||
writeUint32(buf, skill.ID)
|
||||
writeUint32(buf, skill.PP)
|
||||
}
|
||||
} else {
|
||||
writeUint32(buf, uint32(maxInt32(attackVal.RemainHp)))
|
||||
writeUint32(buf, attackVal.MaxHp)
|
||||
writeUint32(buf, uint32(len(attackVal.SkillList)))
|
||||
for _, skill := range attackVal.SkillList {
|
||||
writeUint32(buf, skill.ID)
|
||||
writeUint32(buf, skill.PP)
|
||||
}
|
||||
}
|
||||
writeUint32(buf, attackVal.State)
|
||||
if isAttacker {
|
||||
writeUint32(buf, attackVal.IsCritical)
|
||||
writeUint32(buf, attackVal.State)
|
||||
writeUint32(buf, 1)
|
||||
writeInt32(buf, int32(attackVal.LostHp))
|
||||
writeInt32(buf, attackVal.GainHp)
|
||||
}
|
||||
writeUint32(buf, 0)
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacyGroupBoutDone() {
|
||||
var buf bytes.Buffer
|
||||
if f == nil || !f.LegacyGroupProtocol {
|
||||
return
|
||||
}
|
||||
writeUint32(&buf, f.Round)
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
if ff == nil || ff.Player == nil {
|
||||
return
|
||||
}
|
||||
sendLegacyPacket(ff.Player, groupCmdBoutDone, buf.Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacySpriteDie(in *input.Input, hasBackup bool) {
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
side uint8
|
||||
data uint32
|
||||
)
|
||||
if f == nil || !f.LegacyGroupProtocol || in == nil {
|
||||
return
|
||||
}
|
||||
side = 1
|
||||
if !f.isOurPlayerID(in.UserID) {
|
||||
side = 2
|
||||
}
|
||||
if hasBackup {
|
||||
data = 1
|
||||
}
|
||||
writeUint8(&buf, 1)
|
||||
writeUint8(&buf, side)
|
||||
writeUint8(&buf, uint8(in.TeamSlotIndex()))
|
||||
writeUint8(&buf, 1)
|
||||
writeUint32(&buf, data)
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
if ff == nil || ff.Player == nil {
|
||||
return
|
||||
}
|
||||
sendLegacyPacket(ff.Player, groupCmdSpriteDie, buf.Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func maxInt32(v int32) int32 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -10,11 +10,23 @@ type PlayerCaptureContext struct {
|
||||
Guarantees map[int]int // 按分母分组的保底分子 map[denominator]numerator
|
||||
}
|
||||
|
||||
func NewPlayerCaptureContext() *PlayerCaptureContext {
|
||||
return &PlayerCaptureContext{
|
||||
Denominator: 1000,
|
||||
DecayFactor: 0.10,
|
||||
MinDecayNum: 1,
|
||||
Guarantees: make(map[int]int),
|
||||
}
|
||||
}
|
||||
|
||||
// Roll 通用概率判定(带共享保底) 返回成功,基础,保底
|
||||
func (c *PlayerCaptureContext) Roll(numerator, denominator int) (bool, float64, float64) {
|
||||
if denominator <= 0 {
|
||||
return false, 0, 0
|
||||
}
|
||||
if c == nil {
|
||||
c = NewPlayerCaptureContext()
|
||||
}
|
||||
|
||||
base := float64(numerator) / float64(denominator)
|
||||
bonusNumerator := c.getGuaranteeNumerator(denominator)
|
||||
@@ -41,14 +53,29 @@ func (c *PlayerCaptureContext) Roll(numerator, denominator int) (bool, float64,
|
||||
|
||||
// 保底操作
|
||||
func (c *PlayerCaptureContext) getGuaranteeNumerator(denominator int) int {
|
||||
if c == nil || c.Guarantees == nil {
|
||||
return 0
|
||||
}
|
||||
if num, ok := c.Guarantees[denominator]; ok {
|
||||
return num
|
||||
}
|
||||
return 0
|
||||
}
|
||||
func (c *PlayerCaptureContext) increaseGuarantee(denominator int) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
if c.Guarantees == nil {
|
||||
c.Guarantees = make(map[int]int)
|
||||
}
|
||||
c.Guarantees[denominator]++
|
||||
}
|
||||
func (c *PlayerCaptureContext) resetGuarantee(denominator int) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
if c.Guarantees == nil {
|
||||
c.Guarantees = make(map[int]int)
|
||||
}
|
||||
c.Guarantees[denominator] = 0
|
||||
}
|
||||
|
||||
@@ -23,14 +23,15 @@ type FightC struct {
|
||||
ReadyInfo model.NoteReadyToFightInfo
|
||||
//开始战斗信息
|
||||
info.FightStartOutboundInfo
|
||||
Info info.Fightinfo
|
||||
IsReady bool
|
||||
ownerID uint32 // 战斗发起者ID
|
||||
Our []*input.Input // 我方战斗位
|
||||
Opp []*input.Input // 敌方战斗位
|
||||
OurPlayers []common.PlayerI // 我方操作者
|
||||
OppPlayers []common.PlayerI // 敌方操作者
|
||||
Switch map[actionSlotKey]*action.ActiveSwitchAction
|
||||
Info info.Fightinfo
|
||||
IsReady bool
|
||||
LegacyGroupProtocol bool
|
||||
ownerID uint32 // 战斗发起者ID
|
||||
Our []*input.Input // 我方战斗位
|
||||
Opp []*input.Input // 敌方战斗位
|
||||
OurPlayers []common.PlayerI // 我方操作者
|
||||
OppPlayers []common.PlayerI // 敌方操作者
|
||||
Switch map[actionSlotKey]*action.ActiveSwitchAction
|
||||
|
||||
startl sync.Once
|
||||
StartTime time.Time
|
||||
@@ -227,14 +228,44 @@ func (f *FightC) getInputByController(userID uint32, isOpposite bool) *input.Inp
|
||||
func (f *FightC) expectedActionSlots() map[actionSlotKey]struct{} {
|
||||
slots := make(map[actionSlotKey]struct{}, len(f.Our)+len(f.Opp))
|
||||
for _, slot := range f.SideSlots(SideOur) {
|
||||
slots[newActionSlotKey(slot.ControllerUserID, slot.SlotIndex)] = struct{}{}
|
||||
if f.slotNeedsAction(slot.Input) {
|
||||
slots[newActionSlotKey(slot.ControllerUserID, slot.SlotIndex)] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, slot := range f.SideSlots(SideOpp) {
|
||||
slots[newActionSlotKey(slot.ControllerUserID, slot.SlotIndex)] = struct{}{}
|
||||
if f.slotNeedsAction(slot.Input) {
|
||||
slots[newActionSlotKey(slot.ControllerUserID, slot.SlotIndex)] = struct{}{}
|
||||
}
|
||||
}
|
||||
return slots
|
||||
}
|
||||
|
||||
func (f *FightC) sideHasActionableSlots(side int) bool {
|
||||
for _, slot := range f.SideSlots(side) {
|
||||
if f.slotNeedsAction(slot.Input) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FightC) slotNeedsAction(in *input.Input) bool {
|
||||
var bench []*info.BattlePetEntity
|
||||
if in == nil {
|
||||
return false
|
||||
}
|
||||
if current := in.CurrentPet(); current != nil && current.Info.Hp > 0 {
|
||||
return true
|
||||
}
|
||||
bench = in.BenchPets()
|
||||
for _, pet := range bench {
|
||||
if pet != nil && pet.Info.Hp > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FightC) setActionAttackValue(act action.BattleActionI) {
|
||||
if act == nil {
|
||||
return
|
||||
@@ -329,8 +360,16 @@ func (f *FightC) GetOpp(c common.PlayerI) *input.Input {
|
||||
|
||||
// // 获取随机数
|
||||
func (f *FightC) IsFirst(play common.PlayerI) bool {
|
||||
|
||||
return f.TrueFirst.Player == play
|
||||
if f == nil || play == nil {
|
||||
return false
|
||||
}
|
||||
if f.TrueFirst != nil && f.TrueFirst.Player != nil {
|
||||
return f.TrueFirst.Player == play
|
||||
}
|
||||
if f.First != nil && f.First.Player != nil {
|
||||
return f.First.Player == play
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (f *FightC) GetRound() uint32 {
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ func getItemBonus(itemID uint32) float64 {
|
||||
// -1是保底模式,0是锁定模式,》0是衰减模式
|
||||
// Capture 执行捕捉 ,捕捉精灵,使用的道具,模式
|
||||
func (our *Input) Capture(pet *info.BattlePetEntity, ItemID uint32, ownerpet int) (bool, CaptureDetails) {
|
||||
captureCtx := our.Player.GetPlayerCaptureContext()
|
||||
if captureCtx == nil {
|
||||
captureCtx = info.NewPlayerCaptureContext()
|
||||
}
|
||||
|
||||
if getItemBonus(ItemID) >= 255 {
|
||||
return true, CaptureDetails{
|
||||
@@ -69,12 +73,12 @@ func (our *Input) Capture(pet *info.BattlePetEntity, ItemID uint32, ownerpet int
|
||||
|
||||
// 计算基础捕捉率
|
||||
baseRate := our.calcBaseRate(pet, ItemID)
|
||||
denominator := our.Player.GetPlayerCaptureContext().Denominator
|
||||
denominator := captureCtx.Denominator
|
||||
numerator := int(baseRate * float64(denominator))
|
||||
|
||||
// 衰减模式
|
||||
if ownerpet > 0 {
|
||||
decay := math.Pow(1-our.Player.GetPlayerCaptureContext().DecayFactor, float64(ownerpet))
|
||||
decay := math.Pow(1-captureCtx.DecayFactor, float64(ownerpet))
|
||||
baseRate *= decay
|
||||
if baseRate < 0.01 {
|
||||
baseRate = 0.01 // 最低1%成功率
|
||||
@@ -83,7 +87,7 @@ func (our *Input) Capture(pet *info.BattlePetEntity, ItemID uint32, ownerpet int
|
||||
}
|
||||
|
||||
// 走统一保底判定
|
||||
success, basePct, bonusPct := our.Player.Roll(numerator, denominator)
|
||||
success, basePct, bonusPct := captureCtx.Roll(numerator, denominator)
|
||||
|
||||
return success, CaptureDetails{
|
||||
Success: success,
|
||||
@@ -98,8 +102,12 @@ func (our *Input) Capture(pet *info.BattlePetEntity, ItemID uint32, ownerpet int
|
||||
|
||||
// calcBaseA 按公式计算a值
|
||||
func (our *Input) calcBaseA(pet *info.BattlePetEntity, ItemID uint32) int {
|
||||
captureCtx := our.Player.GetPlayerCaptureContext()
|
||||
if captureCtx == nil {
|
||||
captureCtx = info.NewPlayerCaptureContext()
|
||||
}
|
||||
catchRate := gconv.Int(pet.CatchRate)
|
||||
catchRate = (catchRate * our.Player.GetPlayerCaptureContext().Denominator) / 1000 // 归一化到1000分母
|
||||
catchRate = (catchRate * captureCtx.Denominator) / 1000 // 归一化到1000分母
|
||||
if catchRate < 3 {
|
||||
catchRate = 3
|
||||
}
|
||||
|
||||
@@ -19,11 +19,15 @@ func Shuffle[T any](slice []T) {
|
||||
}
|
||||
|
||||
func (our *Input) GetAction() {
|
||||
actorIndex := our.TeamSlotIndex()
|
||||
targetIndex := our.RandomOpponentSlotIndex()
|
||||
target := our.OpponentSlotAt(targetIndex)
|
||||
|
||||
next := our.Exec(func(t Effect) bool {
|
||||
return t.HookAction()
|
||||
})
|
||||
|
||||
scriptCtx := buildBossHookActionContext(our, next)
|
||||
scriptCtx := buildBossHookActionContext(our, target, next)
|
||||
if aiPlayer, ok := our.Player.(*player.AI_player); ok && aiPlayer.BossScript != "" {
|
||||
scriptBoss := &configmodel.BossConfig{Script: aiPlayer.BossScript}
|
||||
nextByScript, err := scriptBoss.RunHookActionScript(scriptCtx)
|
||||
@@ -37,18 +41,18 @@ func (our *Input) GetAction() {
|
||||
return
|
||||
}
|
||||
|
||||
if applyBossScriptAction(our, scriptCtx) {
|
||||
if applyBossScriptAction(our, scriptCtx, actorIndex, targetIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
selfPet := our.FightC.GetCurrPET(our.Player)
|
||||
selfPet := our.FightC.GetCurrPETAt(our.Player, actorIndex)
|
||||
if selfPet == nil {
|
||||
return
|
||||
}
|
||||
if selfPet.Info.Hp <= 0 {
|
||||
for _, v := range our.AllPet {
|
||||
if v.Info.Hp > 0 {
|
||||
our.FightC.ChangePet(our.Player, v.Info.CatchTime)
|
||||
our.FightC.ChangePetAt(our.Player, v.Info.CatchTime, actorIndex)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -68,9 +72,12 @@ func (our *Input) GetAction() {
|
||||
if !s.CanUse() {
|
||||
continue
|
||||
}
|
||||
s.DamageValue = our.CalculatePower(our.Opp, s)
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
s.DamageValue = our.CalculatePower(target, s)
|
||||
|
||||
oppPet := our.Opp.CurrentPet()
|
||||
oppPet := target.CurrentPet()
|
||||
if oppPet == nil {
|
||||
continue
|
||||
}
|
||||
@@ -99,13 +106,13 @@ func (our *Input) GetAction() {
|
||||
}
|
||||
|
||||
if usedskill != nil {
|
||||
our.FightC.UseSkill(our.Player, uint32(usedskill.XML.ID))
|
||||
our.FightC.UseSkillAt(our.Player, uint32(usedskill.XML.ID), actorIndex, targetIndex)
|
||||
} else {
|
||||
our.FightC.UseSkill(our.Player, 0)
|
||||
our.FightC.UseSkillAt(our.Player, 0, actorIndex, targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func buildBossHookActionContext(our *Input, hookAction bool) *configmodel.BossHookActionContext {
|
||||
func buildBossHookActionContext(our, opponent *Input, hookAction bool) *configmodel.BossHookActionContext {
|
||||
ctx := &configmodel.BossHookActionContext{
|
||||
HookAction: hookAction,
|
||||
Action: "auto",
|
||||
@@ -150,8 +157,8 @@ func buildBossHookActionContext(our *Input, hookAction bool) *configmodel.BossHo
|
||||
if our.AttackValue != nil {
|
||||
ctx.OurAttack = convertAttackValue(our.AttackValue)
|
||||
}
|
||||
if our.Opp != nil {
|
||||
if oppPet := our.Opp.CurrentPet(); oppPet != nil {
|
||||
if opponent != nil {
|
||||
if oppPet := opponent.CurrentPet(); oppPet != nil {
|
||||
ctx.Opp = &configmodel.BossHookPetContext{
|
||||
PetID: oppPet.Info.ID,
|
||||
CatchTime: oppPet.Info.CatchTime,
|
||||
@@ -159,8 +166,8 @@ func buildBossHookActionContext(our *Input, hookAction bool) *configmodel.BossHo
|
||||
MaxHp: oppPet.Info.MaxHp,
|
||||
}
|
||||
}
|
||||
if our.Opp.AttackValue != nil {
|
||||
ctx.OppAttack = convertAttackValue(our.Opp.AttackValue)
|
||||
if opponent.AttackValue != nil {
|
||||
ctx.OppAttack = convertAttackValue(opponent.AttackValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +202,7 @@ func convertAttackValue(src *playermodel.AttackValue) *configmodel.BossHookAttac
|
||||
}
|
||||
}
|
||||
|
||||
func applyBossScriptAction(our *Input, ctx *configmodel.BossHookActionContext) bool {
|
||||
func applyBossScriptAction(our *Input, ctx *configmodel.BossHookActionContext, actorIndex, targetIndex int) bool {
|
||||
if our == nil || ctx == nil {
|
||||
return false
|
||||
}
|
||||
@@ -204,10 +211,10 @@ func applyBossScriptAction(our *Input, ctx *configmodel.BossHookActionContext) b
|
||||
case "", "auto":
|
||||
return false
|
||||
case "skill", "use_skill", "useskill":
|
||||
our.FightC.UseSkill(our.Player, ctx.SkillID)
|
||||
our.FightC.UseSkillAt(our.Player, ctx.SkillID, actorIndex, targetIndex)
|
||||
return true
|
||||
case "switch", "change_pet", "changepet":
|
||||
our.FightC.ChangePet(our.Player, ctx.CatchTime)
|
||||
our.FightC.ChangePetAt(our.Player, ctx.CatchTime, actorIndex)
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -77,15 +77,27 @@ func (our *Input) Heal(in *Input, ac action.BattleActionI, value alpacadecimal.D
|
||||
|
||||
if healValue >= 0 {
|
||||
currentPet.Info.ModelHP(int64(healValue))
|
||||
if our.AttackValue != nil {
|
||||
our.AttackValue.RemainHp = int32(currentPet.Info.Hp)
|
||||
our.AttackValue.MaxHp = currentPet.Info.MaxHp
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
damage := uint32(-healValue)
|
||||
if damage >= currentPet.Info.Hp {
|
||||
currentPet.Info.Hp = 0
|
||||
if our.AttackValue != nil {
|
||||
our.AttackValue.RemainHp = 0
|
||||
our.AttackValue.MaxHp = currentPet.Info.MaxHp
|
||||
}
|
||||
return
|
||||
}
|
||||
currentPet.Info.Hp -= damage
|
||||
if our.AttackValue != nil {
|
||||
our.AttackValue.RemainHp = int32(currentPet.Info.Hp)
|
||||
our.AttackValue.MaxHp = currentPet.Info.MaxHp
|
||||
}
|
||||
|
||||
}
|
||||
func (our *Input) HealPP(value int) {
|
||||
@@ -227,6 +239,10 @@ func (our *Input) Damage(in *Input, sub *info.DamageZone) {
|
||||
} else {
|
||||
currentPet.Info.Hp = currentPet.Info.Hp - uint32(sub.Damage.IntPart())
|
||||
}
|
||||
if our.AttackValue != nil {
|
||||
our.AttackValue.RemainHp = int32(currentPet.Info.Hp)
|
||||
our.AttackValue.MaxHp = currentPet.Info.MaxHp
|
||||
}
|
||||
|
||||
//todo 待实现死亡effet
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package input
|
||||
|
||||
import "github.com/gogf/gf/v2/util/grand"
|
||||
|
||||
// TeamSlots 返回当前输入所在阵营的全部有效战斗位视图。
|
||||
func (our *Input) TeamSlots() []*Input {
|
||||
if our == nil {
|
||||
@@ -18,6 +20,19 @@ func (our *Input) TeamSlots() []*Input {
|
||||
return slots
|
||||
}
|
||||
|
||||
// TeamSlotIndex 返回当前输入在本阵营中的原始站位下标。
|
||||
func (our *Input) TeamSlotIndex() int {
|
||||
if our == nil || len(our.Team) == 0 {
|
||||
return 0
|
||||
}
|
||||
for idx, teammate := range our.Team {
|
||||
if teammate == our {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Teammates 返回队友列表,不包含自己。
|
||||
func (our *Input) Teammates() []*Input {
|
||||
if our == nil {
|
||||
@@ -53,3 +68,55 @@ func (our *Input) LivingTeammates() []*Input {
|
||||
func (our *Input) HasLivingTeammate() bool {
|
||||
return len(our.LivingTeammates()) > 0
|
||||
}
|
||||
|
||||
// OpponentSlotAt 返回指定敌方站位。
|
||||
func (our *Input) OpponentSlotAt(index int) *Input {
|
||||
if our == nil {
|
||||
return nil
|
||||
}
|
||||
if index >= 0 && index < len(our.OppTeam) {
|
||||
return our.OppTeam[index]
|
||||
}
|
||||
if index == 0 {
|
||||
return our.Opp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RandomOpponentSlotIndex 返回一个可用的敌方站位下标,优先从存活站位中随机。
|
||||
func (our *Input) RandomOpponentSlotIndex() int {
|
||||
if our == nil {
|
||||
return 0
|
||||
}
|
||||
if len(our.OppTeam) == 0 {
|
||||
if our.Opp != nil {
|
||||
return 0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
living := make([]int, 0, len(our.OppTeam))
|
||||
available := make([]int, 0, len(our.OppTeam))
|
||||
for idx, opponent := range our.OppTeam {
|
||||
if opponent == nil {
|
||||
continue
|
||||
}
|
||||
available = append(available, idx)
|
||||
current := opponent.CurrentPet()
|
||||
if current != nil && current.Info.Hp > 0 {
|
||||
living = append(living, idx)
|
||||
}
|
||||
}
|
||||
|
||||
candidates := living
|
||||
if len(candidates) == 0 {
|
||||
candidates = available
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(candidates) == 1 {
|
||||
return candidates[0]
|
||||
}
|
||||
return candidates[grand.Intn(len(candidates))]
|
||||
}
|
||||
|
||||
@@ -28,3 +28,33 @@ func TestLivingTeammatesFiltersSelfAndDeadSlots(t *testing.T) {
|
||||
t.Fatalf("expected owner to detect living teammate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeamSlotIndexKeepsOriginalSlot(t *testing.T) {
|
||||
first := &Input{}
|
||||
second := &Input{}
|
||||
third := &Input{}
|
||||
|
||||
team := []*Input{first, second, third}
|
||||
first.Team = team
|
||||
second.Team = team
|
||||
third.Team = team
|
||||
|
||||
if got := third.TeamSlotIndex(); got != 2 {
|
||||
t.Fatalf("expected slot index 2, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomOpponentSlotIndexPrefersLivingTarget(t *testing.T) {
|
||||
owner := &Input{}
|
||||
deadOpp := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 0}}}}
|
||||
liveOpp := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 12}}}}
|
||||
|
||||
owner.OppTeam = []*Input{deadOpp, liveOpp}
|
||||
|
||||
if got := owner.RandomOpponentSlotIndex(); got != 1 {
|
||||
t.Fatalf("expected living opponent slot 1, got %d", got)
|
||||
}
|
||||
if got := owner.OpponentSlotAt(1); got != liveOpp {
|
||||
t.Fatalf("expected opponent slot 1 to return live opponent")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"blazing/cool"
|
||||
"blazing/modules/player/model"
|
||||
"context"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -71,6 +72,27 @@ func (f *FightC) battleLoop() {
|
||||
|
||||
f.Round++
|
||||
|
||||
if !f.sideHasActionableSlots(SideOur) {
|
||||
if player := f.primaryOppPlayer(); player != nil {
|
||||
f.WinnerId = player.GetInfo().UserID
|
||||
}
|
||||
f.Reason = model.BattleOverReason.DefaultEnd
|
||||
f.FightOverInfo.WinnerId = f.WinnerId
|
||||
f.FightOverInfo.Reason = f.Reason
|
||||
f.closefight = true
|
||||
break
|
||||
}
|
||||
if !f.sideHasActionableSlots(SideOpp) {
|
||||
if player := f.primaryOurPlayer(); player != nil {
|
||||
f.WinnerId = player.GetInfo().UserID
|
||||
}
|
||||
f.Reason = model.BattleOverReason.DefaultEnd
|
||||
f.FightOverInfo.WinnerId = f.WinnerId
|
||||
f.FightOverInfo.Reason = f.Reason
|
||||
f.closefight = true
|
||||
break
|
||||
}
|
||||
|
||||
expectedSlots := f.expectedActionSlots()
|
||||
actions := f.collectPlayerActions(expectedSlots)
|
||||
if f.closefight {
|
||||
@@ -152,8 +174,11 @@ func (f *FightC) battleLoop() {
|
||||
//大乱斗,给个延迟
|
||||
//<-time.After(1000)
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
|
||||
ff.Player.SendPackCmd(2506, &f.FightOverInfo)
|
||||
if f.LegacyGroupProtocol {
|
||||
f.sendLegacyGroupOver(ff.Player, &f.FightOverInfo)
|
||||
} else {
|
||||
ff.Player.SendPackCmd(2506, &f.FightOverInfo)
|
||||
}
|
||||
|
||||
ff.Player.QuitFight()
|
||||
|
||||
@@ -179,7 +204,7 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{})
|
||||
defer f.closeActionWindow()
|
||||
|
||||
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
|
||||
go f.Opp[0].GetAction()
|
||||
f.triggerNPCActions()
|
||||
}
|
||||
|
||||
waitr := time.Duration(f.waittime)*time.Millisecond*10 + 30*time.Second
|
||||
@@ -262,7 +287,7 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{})
|
||||
f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction)
|
||||
f.Our[0].Player.SendPackCmd(2407, &ret.Reason)
|
||||
//println("AI出手死切")
|
||||
go f.Opp[0].GetAction() //boss出手后获取出招
|
||||
f.triggerNPCActions() // boss出手后获取出招
|
||||
|
||||
}
|
||||
continue
|
||||
@@ -311,7 +336,11 @@ func (f *FightC) handleTimeout(expectedSlots map[actionSlotKey]struct{}, actions
|
||||
}
|
||||
player := f.getPlayerByID(key.PlayerID)
|
||||
if player != nil {
|
||||
f.UseSkillAt(player, 0, key.ActorIndex, 0)
|
||||
targetIndex := 0
|
||||
if self := f.getInputByUserID(key.PlayerID, key.ActorIndex, false); self != nil {
|
||||
targetIndex = self.RandomOpponentSlotIndex()
|
||||
}
|
||||
f.UseSkillAt(player, 0, key.ActorIndex, targetIndex)
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -353,6 +382,22 @@ func (f *FightC) handleTimeout(expectedSlots map[actionSlotKey]struct{}, actions
|
||||
|
||||
}
|
||||
|
||||
func (f *FightC) triggerNPCActions() {
|
||||
for slot, opponent := range f.Opp {
|
||||
if opponent == nil {
|
||||
continue
|
||||
}
|
||||
go func(slot int, opponent *input.Input) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
cool.Logger.Error(context.Background(), "fight npc action panic", f.ownerID, slot, err, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
opponent.GetAction()
|
||||
}(slot, opponent)
|
||||
}
|
||||
}
|
||||
|
||||
func flattenActionMap(actions map[actionSlotKey]action.BattleActionI) []action.BattleActionI {
|
||||
flattened := make([]action.BattleActionI, 0, len(actions))
|
||||
for _, act := range actions {
|
||||
@@ -599,7 +644,7 @@ func (f *FightC) handleSkillActions(a1, a2 action.BattleActionI) {
|
||||
|
||||
switch {
|
||||
case s1 == nil || s1.SkillEntity == nil:
|
||||
if s2.SkillEntity != nil {
|
||||
if s2 != nil && s2.SkillEntity != nil {
|
||||
if s2.XML.CD != nil {
|
||||
f.waittime = *s2.XML.CD
|
||||
}
|
||||
@@ -608,7 +653,7 @@ func (f *FightC) handleSkillActions(a1, a2 action.BattleActionI) {
|
||||
f.enterturn(s2, nil)
|
||||
// fmt.Println("1 空过 2玩家执行技能:", s2.PlayerID, s2.Info.ID)
|
||||
case s2 == nil || s2.SkillEntity == nil:
|
||||
if s1.SkillEntity != nil {
|
||||
if s1 != nil && s1.SkillEntity != nil {
|
||||
if s1.XML.CD != nil {
|
||||
f.waittime = *s1.XML.CD
|
||||
}
|
||||
|
||||
@@ -47,6 +47,41 @@ func NewFightSingleControllerN(
|
||||
)
|
||||
}
|
||||
|
||||
// NewLegacyGroupFightSingleControllerN 创建旧组队协议的单人控制多站位战斗。
|
||||
func NewLegacyGroupFightSingleControllerN(
|
||||
ourController common.PlayerI,
|
||||
oppController common.PlayerI,
|
||||
ourPetsBySlot [][]model.PetInfo,
|
||||
oppPetsBySlot [][]model.PetInfo,
|
||||
fn func(model.FightOverInfo),
|
||||
) (*FightC, errorcode.ErrorCode) {
|
||||
if ourController == nil || oppController == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||
}
|
||||
fightInfo := ourController.Getfightinfo()
|
||||
|
||||
ourInputs, err := buildSideInputsByController(ourController, ourPetsBySlot, fightInfo.Mode)
|
||||
if err > 0 {
|
||||
return nil, err
|
||||
}
|
||||
oppInputs, err := buildSideInputsByController(oppController, oppPetsBySlot, fightInfo.Mode)
|
||||
if err > 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewFightWithOptions(
|
||||
WithFightInputs(ourInputs, oppInputs),
|
||||
WithFightPlayersOnSide(
|
||||
[]common.PlayerI{ourController},
|
||||
[]common.PlayerI{oppController},
|
||||
),
|
||||
WithInputControllerBinding(InputControllerBindingSingle),
|
||||
WithLegacyGroupProtocol(true),
|
||||
WithFightCallback(fn),
|
||||
WithFightInfo(fightInfo),
|
||||
)
|
||||
}
|
||||
|
||||
// NewFightPerSlotControllerN 创建 N 打战斗(多人各控制一个站位)。
|
||||
// ourPlayers/oppPlayers 与 ourPetsBySlot/oppPetsBySlot 按站位一一对应。
|
||||
func NewFightPerSlotControllerN(
|
||||
@@ -116,6 +151,7 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
|
||||
|
||||
f := &FightC{}
|
||||
f.ownerID = opts.owner.GetInfo().UserID
|
||||
f.LegacyGroupProtocol = opts.legacyGroupProtocol
|
||||
f.OurPlayers = opts.ourPlayers
|
||||
f.OppPlayers = opts.oppPlayers
|
||||
f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction)
|
||||
@@ -160,9 +196,13 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
|
||||
}
|
||||
f.FightStartOutboundInfo = f.buildFightStartInfo()
|
||||
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
ff.Player.SendPackCmd(2503, &f.ReadyInfo)
|
||||
})
|
||||
if f.LegacyGroupProtocol {
|
||||
f.sendLegacyGroupReady()
|
||||
} else {
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
ff.Player.SendPackCmd(2503, &f.ReadyInfo)
|
||||
})
|
||||
}
|
||||
|
||||
cool.Cron.AfterFunc(loadtime, func() {
|
||||
our := f.primaryOur()
|
||||
@@ -180,7 +220,11 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
|
||||
f.WinnerId = opp.Player.GetInfo().UserID
|
||||
}
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
ff.Player.SendPackCmd(2506, &f.FightOverInfo)
|
||||
if f.LegacyGroupProtocol {
|
||||
f.sendLegacyGroupOver(ff.Player, &f.FightOverInfo)
|
||||
} else {
|
||||
ff.Player.SendPackCmd(2506, &f.FightOverInfo)
|
||||
}
|
||||
ff.Player.QuitFight()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type fightBuildOptions struct {
|
||||
fightInfo *info.Fightinfo
|
||||
|
||||
controllerBinding int
|
||||
legacyGroupProtocol bool
|
||||
}
|
||||
|
||||
// defaultFightBuildOptions 返回建战默认参数。
|
||||
@@ -89,6 +90,13 @@ func WithInputControllerBinding(mode int) FightOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLegacyGroupProtocol 设置是否使用旧组队协议 CMD。
|
||||
func WithLegacyGroupProtocol(enabled bool) FightOption {
|
||||
return func(opts *fightBuildOptions) {
|
||||
opts.legacyGroupProtocol = enabled
|
||||
}
|
||||
}
|
||||
|
||||
func NewFightWithOptions(opts ...FightOption) (*FightC, errorcode.ErrorCode) {
|
||||
buildOpts := defaultFightBuildOptions()
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"blazing/logic/service/fight/info"
|
||||
spaceinfo "blazing/logic/service/space/info"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
type baseplayer struct {
|
||||
@@ -85,6 +87,21 @@ func (f *baseplayer) GetPlayerCaptureContext() *info.PlayerCaptureContext {
|
||||
return f.PlayerCaptureContext
|
||||
}
|
||||
|
||||
func (f *baseplayer) Roll(numerator, denominator int) (bool, float64, float64) {
|
||||
if denominator <= 0 {
|
||||
return false, 0, 0
|
||||
}
|
||||
if numerator < 0 {
|
||||
numerator = 0
|
||||
}
|
||||
if numerator > denominator {
|
||||
numerator = denominator
|
||||
}
|
||||
|
||||
base := float64(numerator) / float64(denominator) * 100
|
||||
return grand.Intn(denominator) < numerator, base, 0
|
||||
}
|
||||
|
||||
// FindPet 根据捕捉时间查找宠物
|
||||
// 返回值: (索引, 宠物信息, 是否找到)
|
||||
func (f *baseplayer) FindPet(catchTime uint32) (int, *model.PetInfo, bool) {
|
||||
|
||||
@@ -31,6 +31,8 @@ type MapNode struct {
|
||||
|
||||
IsBroadcast uint32 `gorm:"type:int;default:0;comment:'广播模型ID(0表示不广播)'" json:"is_broadcast"`
|
||||
|
||||
IsGroupBoss uint32 `gorm:"type:int;default:0;comment:'是否为组队Boss(0否1是)'" json:"is_group_boss" description:"是否为组队Boss"`
|
||||
|
||||
TriggerPlotID uint32 `gorm:"default:0;comment:'触发剧情ID(0表示无剧情)'" json:"trigger_plot_id" description:"触发剧情ID"`
|
||||
|
||||
BossIds []uint32 `gorm:"type:jsonb;comment:'塔层BOSS ID列表'" json:"boss_ids"`
|
||||
|
||||
Reference in New Issue
Block a user