diff --git a/docs/fight-group-implementation-checklist-2026-04-04.md b/docs/fight-group-implementation-checklist-2026-04-04.md index dceba324c..96419e024 100644 --- a/docs/fight-group-implementation-checklist-2026-04-04.md +++ b/docs/fight-group-implementation-checklist-2026-04-04.md @@ -17,8 +17,9 @@ ### 1.2 本清单目标 - 在不破坏现有 `1v1` 的前提下,落地组队战斗可运行版本(MVP)。 -- 对齐 `flash` 组队战斗关键协议与关键行为(开战、出招、切宠、道具、结算、战斗结束)。 -- 留出兼容层,允许旧 `24xx/25xx` 流程继续使用。 +- 对齐 `flash`/社区实现中的关键行为(开战、出招、切宠、道具、结算、战斗结束)。 +- 协议层采用“一个统一结构体 + phase 字段”方案,单打/双打共用同一序列化模型。 +- 保留旧 `24xx/25xx` 流程入口,通过服务端适配映射到统一结构体。 ### 1.3 非目标 @@ -49,74 +50,58 @@ - 回合主链仍以双动作兼容流程为中心 - 组队相关特性存在 TODO(例如 `501/502/503`) +### 2.3 外部实现参考(本次新增) + +- `arcadia-star/seer2-fight-ui` + - 双打核心模型不是独立命令集,而是统一帧模型 + `uiStyle + side + position`。 + - `uiStyle` 支持 `2v2/2v1`,战位通过 `position(main/sub)` 区分。 +- `arcadia-star/seer2-next-message/src/entity/fight.rs` + - 采用统一战斗实体结构:`team/user/pet` + `side/position`。 + - 行为包拆分为 `Load/Hurt/Change/Escape/...`,但底层字段模型统一。 +- `ukuq/seer2-server/src/seer2/fight` + - `ArenaResourceLoadCMD -> TeamInfo -> FightUserInfo -> FighterInfo` 为层级化统一结构。 + - `FighterInfo` 直接包含 `position/hp/maxHp/anger/skills`,适合直接映射为本项目统一结构体。 + --- ## 3. 协议对齐清单(按优先级) -> 说明:命令号来源于 `flash` 的 `4c07fa07:CommandID.as`。 -> 实施时可先做“服务端可跑+字段兼容”,逐步补全所有字段。 +> 说明:本清单改为“统一协议结构体”路线,不再强制先实现 `75xx` 独立命令族。 +> 推荐做法:保留旧入口命令,服务端内部统一转为 `FightActionEnvelope/FightStateEnvelope`。 ### 3.1 P0 必做(MVP 必须) -- [ ] `7555 GROUP_READY_TO_FIGHT` - - 用途:组队成员准备 - - 入参建议:`room/groupId + userId + actorSlotsReady` - - 出参建议:广播 ready 状态变化 - -- [ ] `7557 GROUP_START_FIGHT` - - 用途:下发组队开战结构 - - 参考客户端结构: - - `isVsNPC` - - `groupOneMembCnt + groupOneList[]` - - `groupTwoMembCnt + groupTwoList[]` - - `group list` 每项至少要有:`side/pos/userID/petID/catchTime/hp/maxHP/level/catchType` - -- [ ] `7558 GROUP_FT_USE_SKILL` - - 用途:组队技能指令 - - 参考客户端 payload:`skillId(uint32) + side(byte) + pos(byte)`(6字节) - - 服务端内部映射:`actorIndex + targetIndex` - -- [ ] `7563 GROUP_FT_CHG_PET` - - 用途:组队切宠请求 - - 至少支持:按操作者位切宠 - -- [ ] `7562 GROUP_FT_USE_ITEM` - - 用途:组队用道具 - - 至少支持:按 actor/target 生效 - -- [ ] `7560 GROUP_FT_OVER` - - 用途:组队战斗结束 - - 至少下发:结束原因、胜方标识、结算主体 +- [ ] 统一入站动作结构 `FightActionEnvelope` + - 最少字段:`actionType/actorIndex/targetIndex/skillId/itemId/catchTime/escape/chat` + - 兼容映射: + - `2405 -> actionType=skill` + - `2406 -> actionType=item` + - `2407 -> actionType=change` + - `2410 -> actionType=escape` +- [ ] 统一出站状态结构 `FightStateEnvelope` + - 最少字段: + - `phase`(`start/skill_hurt/change/over/load/chat`) + - `left[]/right[]`(元素为统一 `FighterState`) + - `meta`(回合号、天气、胜负、结束原因) +- [ ] 统一战位子结构 `FighterState` + - 每项至少包含:`side/position(userSlot)/userId/petId(catchTime)/hp/maxHp/level/anger/status/prop/skills` ### 3.2 P1 强烈建议(提升一致性) -- [ ] `7559 GROUP_FT_SKILL_HURT` - - 对齐技能伤害播报(攻击方+受击方快照) - - 参考结构包含:状态数组、能力等级、技能、HP 变化、暴击、伤害值 - -- [ ] `7567 GROUP_FT_CHG_PET_SUC` - - 切宠成功广播,保障前端战位同步 - -- [ ] `7568 GROUP_FT_ESCAPE_SUC` - - 逃跑成功广播(含退出战位) - -- [ ] `7570 GROUP_CHAT_MSG` - - 组队战斗聊天转发 - -- [ ] `7571/7572 GROUP_LOAD_PERCENT(_NOTICE)` - - 组队加载进度同步 +- [ ] 完善 `phase=skill_hurt` + - 至少带:施法方快照、受击方快照、技能、暴击、伤害、HP 变更 +- [ ] 完善 `phase=change` + - 至少带:切宠发起位、切入目标位、新精灵状态 +- [ ] 完善 `phase=over` + - 至少带:结束原因、胜方、收益主体 +- [ ] 完善 `phase=load/chat` + - 组队加载进度、战斗内聊天统一走同一 envelope ### 3.3 P2 视时间补齐 -- [ ] `7561 GROUP_FT_SPRITE_DIE` -- [ ] `7573 GROUP_FT_SPRITE_NOTICE` -- [ ] `7574 GROUP_FT_WIN_CLOSE` -- [ ] `7575 GP_SKILL_WAIT_PERT` -- [ ] `7576 GP_SKILL_WAIT_NOTICE` -- [ ] `7585 GROUP_FT_OVERTIME_NOTICE` -- [ ] `7586 GROUP_FT_SKILL_PLAY_OVER` -- [ ] `7587 GROUP_FT_TIME_OUT_EXIT` -- [ ] `7588 GROUP_FT_RELATION_NOTICE` +- [ ] `phase=sprite_die/sprite_notice/win_close` +- [ ] `phase=skill_wait/skill_wait_notice` +- [ ] `phase=overtime/timeout_exit/relation_notice` --- @@ -124,13 +109,13 @@ ## 4.1 协议与结构层(Owner A) -- [ ] 新增组队协议结构文件 - - 建议新建:`logic/service/fight/cmd_group.go` - - 要求:所有组队命令入站结构都有 `TomeeHeader cmd:"xxxx"` +- [ ] 新增统一协议结构文件 + - 建议新建:`logic/service/fight/cmd_unified.go` + - 要求:统一定义 `FightActionEnvelope` 和映射辅助结构 -- [ ] 新增组队出站结构 - - 建议新建:`logic/service/fight/info/group_info.go` - - 要求:明确 `side/pos/user/pet/hp/maxHp/level` 等核心字段 +- [ ] 新增统一出站结构 + - 建议新建:`logic/service/fight/info/unified_info.go` + - 要求:定义 `FightStateEnvelope/FighterState`,支持单打与双打 - [ ] 统一战位字段命名规范 - `actorIndex`:我方执行位 @@ -139,26 +124,20 @@ 验收: -- [ ] `controller.Init(true)` 能注册全部新命令,无重复 cmd panic。 -- [ ] 对每个新 cmd,可反序列化入参并进入控制器方法。 +- [ ] 旧 cmd(`2405/2406/2407/2410`)可无损映射到统一入站结构。 +- [ ] 统一出站结构在 `start/skill_hurt/change/over` phase 均可序列化。 --- ## 4.2 控制器与路由层(Owner B) -- [ ] 新增组队战斗控制器 - - 建议新建:`logic/controller/fight_group.go` - - 包含: - - 组队准备 - - 组队出招 - - 组队切宠 - - 组队道具 - - 组队逃跑 - - 组队聊天 +- [ ] 新增统一动作入口(可单文件) + - 建议新建:`logic/controller/fight_unified.go` + - 用途:将旧包和未来扩展包统一落到 `FightActionEnvelope` - [ ] 兼容旧协议入口 - `2405/2406/2407` 保持可用(默认 `actorIndex=0,targetIndex=0`) - - 新 `75xx` 走组队专用入口 + - 组队场景由 `actorIndex/targetIndex` 与战斗上下文决定,不再依赖独立 `75xx` - [ ] 增加战前校验 - 成员是否在同一组队房间 @@ -167,7 +146,7 @@ 验收: -- [ ] 双端同时发送 `7558` 能转化为 `UseSkillAt(...)`。 +- [ ] 任意技能动作都能转化为 `UseSkillAt(...)`(含 `actorIndex/targetIndex`)。 - [ ] 非法战位命令被拒绝,不影响其他战位。 --- @@ -215,8 +194,8 @@ - 战斗结束广播 - [ ] 保留旧包兼容(必要时双发) - - 组队战斗对新包 - - 非组队仍可走旧 `2503/2505/2506` + - 单打/双打统一走同一结构体 + - 如前端未升级,可按需保留旧 `2503/2505/2506` 过渡映射 验收: @@ -276,14 +255,14 @@ - 协议/结构: - `logic/service/fight/cmd.go` - - `logic/service/fight/cmd_group.go`(新增) + - `logic/service/fight/cmd_unified.go`(新增) - `logic/service/fight/info/info.go` - - `logic/service/fight/info/group_info.go`(新增) + - `logic/service/fight/info/unified_info.go`(新增) - 控制器: - `logic/controller/fight_base.go` - `logic/controller/fight_pvp_withplayer.go` - - `logic/controller/fight_group.go`(新增) + - `logic/controller/fight_unified.go`(新增) - 核心流程: - `logic/service/fight/new.go` @@ -309,8 +288,8 @@ ### M1(协议可通) -- [ ] 75xx 关键命令可收发 -- [ ] 控制器能转发到 `FightC` indexed 接口 +- [ ] 统一结构体可完成 `start/skill_hurt/change/over` 四类下发 +- [ ] 旧命令入口均可映射到 `FightC` indexed 接口 ### M2(核心可跑) @@ -339,7 +318,7 @@ 缓解:把“回合开始/结束”从 pair 执行中抽离,确保每回合只触发一次。 - 风险:协议切换导致旧客户端不可用。 - 缓解:保留旧包并按战斗类型分流;必要时过渡期双发。 + 缓解:服务端保持旧入口不变,先做“旧包 -> 统一结构”映射;前端按版本切流。 - 风险:effect 批量改动引发回归。 缓解:先做组队关键 effect,其他 effect 分批迁移并每批回归。 @@ -363,3 +342,16 @@ - 如改动协议字段,必须附抓包样例或字段注释,不允许只改代码不补说明。 - 如发现与本清单冲突的历史逻辑,以“兼容线上行为优先”,并在文档记录偏差原因。 +--- + +## 10. 可实现性结论(统一协议结构体) + +- 结论:可实现,且风险可控。 +- 依据: + - `seer2-fight-ui` 的双打模型本质是统一数据结构 + `uiStyle/side/position`,不是强依赖独立命令族。 + - `seer2-next-message` 与 `seer2-server` 都采用统一 `team/user/pet` 层级结构,`position` 作为战位核心字段。 + - 本仓库已具备 `actorIndex/targetIndex` 与 `UseSkillAt/ChangePetAt/UseItemAt` 能力,协议统一后只需补齐映射和广播。 +- 实施建议: + - 先完成“旧入口 -> 统一入站结构”映射。 + - 再完成“统一出站结构 + phase 广播”。 + - 最后做前端切换与旧包退场(或长期双通道兼容)。 diff --git a/logic/service/fight/action.go b/logic/service/fight/action.go index 608d8bec6..b4b520da3 100644 --- a/logic/service/fight/action.go +++ b/logic/service/fight/action.go @@ -280,15 +280,10 @@ func (f *FightC) buildFightStartInfo() info.FightStartOutboundInfo { startInfo := info.FightStartOutboundInfo{} ourInfos := f.collectFightPetInfos(f.Our) oppInfos := f.collectFightPetInfos(f.Opp) - startInfo.Infos = append(startInfo.Infos, ourInfos...) - startInfo.Infos = append(startInfo.Infos, oppInfos...) - startInfo.InfoLen = uint32(len(startInfo.Infos)) - if len(ourInfos) > 0 { - startInfo.Info1 = ourInfos[0] - } - if len(oppInfos) > 0 { - startInfo.Info2 = oppInfos[0] - } + startInfo.Info1 = append(startInfo.Info1, ourInfos...) + startInfo.Info2 = append(startInfo.Info2, oppInfos...) + startInfo.Info1Len = uint32(len(startInfo.Info1)) + startInfo.Info2Len = uint32(len(startInfo.Info2)) return startInfo } diff --git a/logic/service/fight/effect/1263_1287.go b/logic/service/fight/effect/1263_1287.go index 395f30808..fc5bfebfc 100644 --- a/logic/service/fight/effect/1263_1287.go +++ b/logic/service/fight/effect/1263_1287.go @@ -63,11 +63,11 @@ func (e *Effect1263) Skill_Use() bool { } divisor := e.Args()[0] - if e.CarrierInput().CurPet[0].GetHP().Cmp(e.OpponentInput().CurPet[0].GetHP()) < 0 { + if e.CarrierPet().GetHP().Cmp(e.OpponentPet().GetHP()) < 0 { divisor = e.Args()[1] } - damage := e.OpponentInput().CurPet[0].GetMaxHP().Div(divisor) + damage := e.OpponentPet().GetMaxHP().Div(divisor) if damage.Cmp(alpacadecimal.Zero) <= 0 { return true } @@ -208,7 +208,7 @@ type Effect1268 struct { } func (e *Effect1268) Skill_Use() bool { - if len(e.Args()) == 0 || e.OpponentInput().CurPet[0].Info.Hp > 0 { + if len(e.Args()) == 0 || e.OpponentPet().Info.Hp > 0 { return true } effect := e.CarrierInput().InitEffect(input.EffectType.Sub, 1268, int(e.Args()[0].IntPart())) @@ -246,7 +246,7 @@ func (e *Effect1269) SkillHit_ex() bool { return true } - damage := e.OpponentInput().CurPet[0].GetMaxHP().Div(e.Args()[1]) + damage := e.OpponentPet().GetMaxHP().Div(e.Args()[1]) if damage.Cmp(alpacadecimal.Zero) <= 0 { return true } @@ -383,10 +383,10 @@ func (e *Effect1273) OnSkill() bool { if len(e.Args()) == 0 || e.Args()[0].Cmp(alpacadecimal.Zero) <= 0 { return true } - if e.CarrierInput().CurPet[0].GetHP().Cmp(e.CarrierInput().CurPet[0].GetMaxHP()) != 0 { + if e.CarrierPet().GetHP().Cmp(e.CarrierPet().GetMaxHP()) != 0 { return true } - damage := e.CarrierInput().CurPet[0].GetMaxHP().Div(e.Args()[0]) + damage := e.CarrierPet().GetMaxHP().Div(e.Args()[0]) if damage.Cmp(alpacadecimal.Zero) <= 0 { return true } @@ -423,7 +423,7 @@ func (e *Effect1275) Skill_Use() bool { if len(e.Args()) == 0 { return true } - e.CarrierInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: e.CarrierInput().CurPet[0].GetHP()}) + e.CarrierInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: e.CarrierPet().GetHP()}) effect := e.CarrierInput().InitEffect(input.EffectType.Sub, 1275, int(e.Args()[0].IntPart())) if effect != nil { e.CarrierInput().AddEffect(e.CarrierInput(), effect) @@ -468,8 +468,8 @@ func (e *Effect1276) Skill_Use() bool { if len(e.Args()) == 0 || e.Args()[0].Cmp(alpacadecimal.Zero) <= 0 { return true } - damage := e.CarrierInput().CurPet[0].GetHP().Div(e.Args()[0]) - e.CarrierInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: e.CarrierInput().CurPet[0].GetHP()}) + damage := e.CarrierPet().GetHP().Div(e.Args()[0]) + e.CarrierInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: e.CarrierPet().GetHP()}) if damage.Cmp(alpacadecimal.Zero) > 0 { e.OpponentInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Percent, Damage: damage}) } @@ -482,16 +482,16 @@ type Effect1277 struct { } func (e *Effect1277) Skill_Use() bool { - if e.CarrierInput().CurPet[0].GetHP().Cmp(e.OpponentInput().CurPet[0].GetHP()) > 0 { + if e.CarrierPet().GetHP().Cmp(e.OpponentPet().GetHP()) > 0 { leave := alpacadecimal.NewFromInt(1) - if e.CarrierInput().CurPet[0].GetHP().Cmp(leave) > 0 { - e.CarrierInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: e.CarrierInput().CurPet[0].GetHP().Sub(leave)}) + if e.CarrierPet().GetHP().Cmp(leave) > 0 { + e.CarrierInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: e.CarrierPet().GetHP().Sub(leave)}) } e.OpponentInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: alpacadecimal.NewFromInt(int64(grand.N(350, 550)))}) return true } - lost := e.CarrierInput().CurPet[0].GetHP() + lost := e.CarrierPet().GetHP() e.CarrierInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: lost}) e.OpponentInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: lost}) applyStatusByID(e.CarrierInput(), e.OpponentInput(), int(info.PetStatus.Paralysis)) @@ -614,7 +614,7 @@ func (e *Effect1280) OnSkill() bool { if percent > max { percent = max } - damage := e.CarrierInput().CurPet[0].GetHP().Mul(alpacadecimal.NewFromInt(int64(percent))).Div(alpacadecimal.NewFromInt(100)) + damage := e.CarrierPet().GetHP().Mul(alpacadecimal.NewFromInt(int64(percent))).Div(alpacadecimal.NewFromInt(100)) if damage.Cmp(alpacadecimal.Zero) > 0 { e.OpponentInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Percent, Damage: damage}) } @@ -634,12 +634,12 @@ func (e *Effect1281) Skill_Use() bool { } e.OpponentInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Fixed, Damage: shield}) if shield.Cmp(alpacadecimal.NewFromInt(300)) > 0 { - dmg := e.CarrierInput().CurPet[0].GetMaxHP().Div(alpacadecimal.NewFromInt(3)) + dmg := e.CarrierPet().GetMaxHP().Div(alpacadecimal.NewFromInt(3)) e.OpponentInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Percent, Damage: dmg}) return true } if shield.Cmp(alpacadecimal.NewFromInt(300)) < 0 { - val := e.CarrierInput().CurPet[0].GetMaxHP().Div(alpacadecimal.NewFromInt(3)) + val := e.CarrierPet().GetMaxHP().Div(alpacadecimal.NewFromInt(3)) e.CarrierInput().Heal(e.CarrierInput(), &action.SelectSkillAction{}, val) e.OpponentInput().Damage(e.CarrierInput(), &info.DamageZone{Type: info.DamageType.Percent, Damage: val}) } @@ -729,7 +729,7 @@ type Effect1285 struct { } func (e *Effect1285) Skill_Use() bool { - if len(e.Args()) == 0 || e.OpponentInput().CurPet[0].Info.Hp == 0 { + if len(e.Args()) == 0 || e.OpponentPet().Info.Hp == 0 { return true } zeroRandomSkillPP(e.OpponentInput(), int(e.Args()[0].IntPart())) diff --git a/logic/service/fight/effect/none.go b/logic/service/fight/effect/none.go index 9287d65f0..5530a2a02 100644 --- a/logic/service/fight/effect/none.go +++ b/logic/service/fight/effect/none.go @@ -68,26 +68,33 @@ func (e *Effect201) OnSkill() bool { healAll := len(args) > 1 && args[0].IntPart() != 0 if !healAll { - e.Ctx().Our.Heal( - e.Ctx().Our, + target := e.TargetInput() + if target == nil { + target = e.Ctx().Our + } + current := target.CurrentPet() + if current == nil { + return true + } + target.Heal( + e.CarrierInput(), &action.SelectSkillAction{}, - e.Ctx().Our.CurPet[0].GetMaxHP().Div(divisor), + current.GetMaxHP().Div(divisor), ) return true } - for _, pet := range e.Ctx().Our.AllPet { - if pet == nil || !pet.Alive() { + team := e.Ctx().Our.Team + if len(team) == 0 { + team = []*input.Input{e.Ctx().Our} + } + for _, ally := range team { + if ally == nil { continue } - - healAmount := pet.GetMaxHP().Div(divisor) - if pet == e.Ctx().Our.CurPet[0] { - e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, healAmount) - continue + if current := ally.CurrentPet(); current != nil && current.Alive() { + ally.Heal(e.CarrierInput(), &action.SelectSkillAction{}, current.GetMaxHP().Div(divisor)) } - - pet.Info.ModelHP(healAmount.IntPart()) } return true diff --git a/logic/service/fight/fightc.go b/logic/service/fight/fightc.go index ea2152823..ce7aada37 100644 --- a/logic/service/fight/fightc.go +++ b/logic/service/fight/fightc.go @@ -35,9 +35,14 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, skill *info }) var originalProps [2][6]int8 var originalPetInfo [2]model.PetInfo + attackerPet := attacker.CurrentPet() + defenderPet := defender.CurrentPet() + if attackerPet == nil || defenderPet == nil { + return + } //复制属性 originalProps[0], originalProps[1] = attacker.Prop, defender.Prop - originalPetInfo[0], originalPetInfo[1] = attacker.CurPet[0].Info, defender.CurPet[0].Info + originalPetInfo[0], originalPetInfo[1] = attackerPet.Info, defenderPet.Info attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //计算变威力 effect.Ctx().SkillEntity = skill @@ -59,7 +64,7 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, skill *info //还原属性 attacker.Prop, defender.Prop = originalProps[0], originalProps[1] - attacker.CurPet[0].Info, defender.CurPet[0].Info = originalPetInfo[0], originalPetInfo[1] + attackerPet.Info, defenderPet.Info = originalPetInfo[0], originalPetInfo[1] if attacker.IsCritical == 1 { //命中了才有暴击 //暴击破防 @@ -130,6 +135,28 @@ func (f *FightC) getSkillParticipants(skillAction *action.SelectSkillAction) (*i return f.GetInputByAction(skillAction, false), f.GetInputByAction(skillAction, true) } +func (f *FightC) collectAttackValues(inputs []*input.Input) []model.AttackValue { + values := make([]model.AttackValue, 0, len(inputs)) + for actorIndex, fighter := range inputs { + if fighter == nil || fighter.AttackValue == nil { + continue + } + attackValue := *fighter.AttackValue + attackValue.ActorIndex = uint32(actorIndex) + values = append(values, attackValue) + } + return values +} + +func (f *FightC) buildNoteUseSkillOutboundInfo() info.NoteUseSkillOutboundInfo { + result := info.NoteUseSkillOutboundInfo{} + result.FirstAttackInfo = append(result.FirstAttackInfo, f.collectAttackValues(f.Our)...) + result.SecondAttackInfo = append(result.SecondAttackInfo, f.collectAttackValues(f.Opp)...) + result.FirstAttackInfoLen = uint32(len(result.FirstAttackInfo)) + result.SecondAttackInfoLen = uint32(len(result.SecondAttackInfo)) + return result +} + // enterturn 处理战斗回合逻辑 // 回合有先手方和后手方,同时有攻击方和被攻击方 func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) { @@ -235,16 +262,18 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) } currentSkill = originalSkill - defender.ExecWithOpponent(attacker, func(effect input.Effect) bool { //这个是能否使用技能 - effect.Ctx().SkillEntity = currentSkill - return effect.ActionStartEx(firstAttack, secondAttack) - }) - canUseSkill := attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //这个是能否使用技能 - effect.Ctx().SkillEntity = currentSkill - return effect.ActionStart(firstAttack, secondAttack) - }) + defender.ExecWithOpponent(attacker, func(effect input.Effect) bool { //这个是能否使用技能 + effect.Ctx().SkillEntity = currentSkill + return effect.ActionStartEx(firstAttack, secondAttack) + }) + canUseSkill := attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //这个是能否使用技能 + effect.Ctx().SkillEntity = currentSkill + return effect.ActionStart(firstAttack, secondAttack) + }) - canUse := canUseSkill && action.CanUse(currentSkill) && attacker != nil && attacker.CurPet[0].Info.Hp > 0 + attackerPet := attacker.CurrentPet() + defenderPet := defender.CurrentPet() + canUse := canUseSkill && action.CanUse(currentSkill) && attacker != nil && attackerPet != nil && attackerPet.Info.Hp > 0 if !canUse { @@ -268,7 +297,7 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) f.processSkillAttack(attacker, defender, currentSkill) currentSkill = originalSkill //还原技能 - _, skill, ok := utils.FindWithIndex(attacker.CurPet[0].Info.SkillList, func(item model.SkillInfo) bool { + _, skill, ok := utils.FindWithIndex(attackerPet.Info.SkillList, func(item model.SkillInfo) bool { return item.ID == currentSkill.Info.ID }) if ok { @@ -281,7 +310,7 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) skill.Use(usecount) } } - if defender.CurPet[0].Info.Hp > 0 { + if defenderPet != nil && defenderPet.Info.Hp > 0 { //技能使用后 defender.ExecWithOpponent(attacker, func(effect input.Effect) bool { //技能使用后的我方效果 effect.Ctx().SkillEntity = currentSkill @@ -290,7 +319,7 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) }) } - if attacker.CurPet[0].Info.Hp > 0 { + if attackerPet != nil && attackerPet.Info.Hp > 0 { //技能使用后 attacker.ExecWithOpponent(defender, func(effect input.Effect) bool { //技能使用后的我方效果 effect.Ctx().SkillEntity = currentSkill @@ -311,16 +340,16 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) return true }) - if defender.CurPet[0].Info.Hp <= 0 && attacker.CurPet[0].Info.Hp <= 0 { //先手方死亡,触发反同归于尽 - attacker.CurPet[0].Info.Hp = 1 + if defenderPet != nil && attackerPet != nil && defenderPet.Info.Hp <= 0 && attackerPet.Info.Hp <= 0 { //先手方死亡,触发反同归于尽 + attackerPet.Info.Hp = 1 } - if defender.CurPet[0].Info.Hp <= 0 { + if defenderPet != nil && defenderPet.Info.Hp <= 0 { f.TURNOVER(defender) break } - if attacker.CurPet[0].Info.Hp <= 0 { + if attackerPet != nil && attackerPet.Info.Hp <= 0 { f.TURNOVER(attacker) break @@ -343,10 +372,7 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) } - attackValueResult := info.AttackValueS{ - FAttack: *f.First.AttackValue, - SAttack: *f.Second.AttackValue, - } + attackValueResult := f.buildNoteUseSkillOutboundInfo() //因为切完才能广播,所以必须和回合结束分开结算 f.Broadcast(func(fighter *input.Input) { for _, switchAction := range f.Switch { diff --git a/logic/service/fight/info/info.go b/logic/service/fight/info/info.go index 647c8b636..fbaf1c73e 100644 --- a/logic/service/fight/info/info.go +++ b/logic/service/fight/info/info.go @@ -103,10 +103,6 @@ type FightPetInfo struct { //能力提升属性 Prop [6]int8 } -type AttackValueS struct { - FAttack model.AttackValue - SAttack model.AttackValue -} func NewAttackValue(userid uint32) *model.AttackValue { return &model.AttackValue{ @@ -200,21 +196,22 @@ type PropDict struct { // NoteUseSkillOutboundInfo 战斗技能使用通知的出站信息结构体 type NoteUseSkillOutboundInfo struct { - FirstAttackInfo model.AttackValue // 本轮先手的精灵在释放技能结束后的状态 - SecondAttackInfo model.AttackValue // 本轮后手的精灵在释放技能结束后的状态 + FirstAttackInfoLen uint32 `struc:"sizeof=FirstAttackInfo"` + FirstAttackInfo []model.AttackValue // 本轮先手方精灵在释放技能结束后的状态 + SecondAttackInfoLen uint32 `struc:"sizeof=SecondAttackInfo"` + SecondAttackInfo []model.AttackValue // 本轮后手方精灵在释放技能结束后的状态 } type FightStartOutboundInfo struct { IsCanAuto uint32 `fieldDesc:"是否自动 默认给0 怀疑是自动战斗器使用的" ` - InfoLen uint32 `struc:"sizeof=Infos"` - Infos []FightPetInfo + Info1Len uint32 `struc:"sizeof=Info1"` + // 我方当前战斗精灵信息(前端通过userid判断是否为我方) + Info1 []FightPetInfo - // 当前战斗精灵信息1(前端通过userid判断是否为我方) - Info1 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"` - - // 当前战斗精灵信息2(前端通过userid判断是否为我方) - Info2 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"` + Info2Len uint32 `struc:"sizeof=Info2"` + // 对方当前战斗精灵信息(前端通过userid判断是否为我方) + Info2 []FightPetInfo } type S2C_2404 struct { diff --git a/logic/service/fight/input.go b/logic/service/fight/input.go index 769ac0d01..82bfa866b 100644 --- a/logic/service/fight/input.go +++ b/logic/service/fight/input.go @@ -162,6 +162,23 @@ func (f *FightC) linkOppInputs() { } } +func (f *FightC) linkTeamViews() { + for _, fighter := range f.Our { + if fighter == nil { + continue + } + fighter.Team = f.Our + fighter.OppTeam = f.Opp + } + for _, fighter := range f.Opp { + if fighter == nil { + continue + } + fighter.Team = f.Opp + fighter.OppTeam = f.Our + } +} + func (f *FightC) getSideInputs(userID uint32, isOpposite bool) []*input.Input { isOur := f.isOurPlayerID(userID) if isOpposite { @@ -177,36 +194,48 @@ func (f *FightC) getSideInputs(userID uint32, isOpposite bool) []*input.Input { } func (f *FightC) findInputByUserID(userID uint32) (*input.Input, bool) { - isOur := f.isOurPlayerID(userID) - if isOur { - if in := f.selectInput(f.Our, 0); in != nil { + for _, in := range f.Our { + if in != nil && in.ControlledBy(userID) { return in, true } - return nil, true } - if in := f.selectInput(f.Opp, 0); in != nil { - return in, false + for _, in := range f.Opp { + if in != nil && in.ControlledBy(userID) { + return in, false + } } return nil, false } func (f *FightC) getInputByUserID(userID uint32, index int, isOpposite bool) *input.Input { - return f.selectInput(f.getSideInputs(userID, isOpposite), index) + selected := f.selectInput(f.getSideInputs(userID, isOpposite), index) + if selected == nil { + return nil + } + // 操作自身站位时,必须由该站位控制者发起。 + if !isOpposite && !selected.ControlledBy(userID) { + return nil + } + return selected +} + +func (f *FightC) getInputByController(userID uint32, isOpposite bool) *input.Input { + sideInputs := f.getSideInputs(userID, isOpposite) + for _, in := range sideInputs { + if in != nil && in.ControlledBy(userID) { + return in + } + } + return f.selectInput(sideInputs, 0) } func (f *FightC) expectedActionSlots() map[actionSlotKey]struct{} { slots := make(map[actionSlotKey]struct{}, len(f.Our)+len(f.Opp)) - for actorIndex, fighter := range f.Our { - if fighter == nil || fighter.Player == nil { - continue - } - slots[newActionSlotKey(fighter.Player.GetInfo().UserID, actorIndex)] = struct{}{} + for _, slot := range f.SideSlots(SideOur) { + slots[newActionSlotKey(slot.ControllerUserID, slot.SlotIndex)] = struct{}{} } - for actorIndex, fighter := range f.Opp { - if fighter == nil || fighter.Player == nil { - continue - } - slots[newActionSlotKey(fighter.Player.GetInfo().UserID, actorIndex)] = struct{}{} + for _, slot := range f.SideSlots(SideOpp) { + slots[newActionSlotKey(slot.ControllerUserID, slot.SlotIndex)] = struct{}{} } return slots } @@ -234,7 +263,7 @@ func (f *FightC) GetInputByPlayer(c common.PlayerI, isOpposite bool) *input.Inpu } return f.primaryOur() } - return f.getInputByUserID(c.GetInfo().UserID, 0, isOpposite) + return f.getInputByController(c.GetInfo().UserID, isOpposite) } func (f *FightC) GetInputByAction(c action.BattleActionI, isOpposite bool) *input.Input { @@ -266,6 +295,14 @@ func (f *FightC) GetCurrPETAt(c common.PlayerI, actorIndex int) *info.BattlePetE } return in.PrimaryCurPet() } + +func (f *FightC) GetCurrPETByAction(c action.BattleActionI, isOpposite bool) *info.BattlePetEntity { + in := f.GetInputByAction(c, isOpposite) + if in == nil { + return nil + } + return in.CurrentPet() +} func (f *FightC) GetOpp(c common.PlayerI) *input.Input { return f.GetInputByPlayer(c, true) } diff --git a/logic/service/fight/input/ai.go b/logic/service/fight/input/ai.go index f7b05b1b0..83bab2bbe 100644 --- a/logic/service/fight/input/ai.go +++ b/logic/service/fight/input/ai.go @@ -58,8 +58,12 @@ func (our *Input) GetAction() { // 计算技能对对方的伤害(假设CalculatePower返回伤害值,或需从技能中获取) s.DamageValue = our.CalculatePower(our.Opp, s) + oppPet := our.Opp.CurrentPet() + if oppPet == nil { + continue + } // 判断是否能秒杀(伤害 >= 对方当前生命值) - if s.DamageValue.Cmp(our.Opp.CurPet[0].GetHP()) != -1 { // 假设oppPet.HP为对方当前剩余生命值 + if s.DamageValue.Cmp(oppPet.GetHP()) != -1 { // 假设oppPet.HP为对方当前剩余生命值 if usedskill != nil { if s.DamageValue.Cmp(usedskill.DamageValue) != -1 { diff --git a/logic/service/fight/input/effect.go b/logic/service/fight/input/effect.go index 11aa7e4ea..508cd1bd9 100644 --- a/logic/service/fight/input/effect.go +++ b/logic/service/fight/input/effect.go @@ -91,9 +91,13 @@ func (our *Input) InitEffect(etype EnumEffectType, id int, a ...int) Effect { // * battle_lv: atk(0), def(1), sp_atk(2), sp_def(3), spd(4), accuracy(5) // 是否需要真实提升 func (our *Input) GetProp(id int) alpacadecimal.Decimal { + currentPet := our.CurrentPet() + if currentPet == nil { + return alpacadecimal.Zero + } // 计算实际值(这里可以插入后续优化的函数调用) - realValue := info.CalculateRealValue(alpacadecimal.NewFromInt(int64(our.CurPet[0].Info.Prop[id])), our.AttackValue.Prop[id]) + realValue := info.CalculateRealValue(alpacadecimal.NewFromInt(int64(currentPet.Info.Prop[id])), our.AttackValue.Prop[id]) // todo: 插入获取后处理函数,例如: // realValue = postProcessValue(realValue, id, c) diff --git a/logic/service/fight/input/fight.go b/logic/service/fight/input/fight.go index a98fcdf73..42cdccbec 100644 --- a/logic/service/fight/input/fight.go +++ b/logic/service/fight/input/fight.go @@ -14,6 +14,11 @@ import ( // 计算暴击 func (our *Input) CalculateCrit(opp *Input, skill *info.SkillEntity) { + ourPet := our.CurrentPet() + oppPet := opp.CurrentPet() + if ourPet == nil || oppPet == nil { + return + } skill.Crit = 0 if skill.Category() == info.Category.STATUS { //属性技能不用算暴击 @@ -30,11 +35,11 @@ func (our *Input) CalculateCrit(opp *Input, skill *info.SkillEntity) { CritRate = 16 } // CritSelfHalfHp: 自身体力低于一半时必定致命一击; 默认: 0 - if skill.XML.CritSelfHalfHp != 0 && (our.CurPet[0].HP < int(our.CurPet[0].Info.MaxHp)/2) { + if skill.XML.CritSelfHalfHp != 0 && (ourPet.HP < int(ourPet.Info.MaxHp)/2) { CritRate = 16 } // CritFoeHalfHp: 对方体力低于一半时必定致命一击; 默认: 0 - if skill.XML.CritSelfHalfHp != 0 && (opp.CurPet[0].HP < int(opp.CurPet[0].Info.MaxHp)/2) { + if skill.XML.CritSelfHalfHp != 0 && (oppPet.HP < int(oppPet.Info.MaxHp)/2) { CritRate = 16 } @@ -47,6 +52,10 @@ func (our *Input) CalculateCrit(opp *Input, skill *info.SkillEntity) { // 恢复血量 func (our *Input) Heal(in *Input, ac action.BattleActionI, value alpacadecimal.Decimal) { + currentPet := our.CurrentPet() + if currentPet == nil { + return + } healValue := int(value.IntPart()) if ac != nil { @@ -67,30 +76,38 @@ func (our *Input) Heal(in *Input, ac action.BattleActionI, value alpacadecimal.D } if healValue >= 0 { - our.CurPet[0].Info.ModelHP(int64(healValue)) + currentPet.Info.ModelHP(int64(healValue)) return } damage := uint32(-healValue) - if damage >= our.CurPet[0].Info.Hp { - our.CurPet[0].Info.Hp = 0 + if damage >= currentPet.Info.Hp { + currentPet.Info.Hp = 0 return } - our.CurPet[0].Info.Hp -= damage + currentPet.Info.Hp -= damage } func (our *Input) HealPP(value int) { + currentPet := our.CurrentPet() + if currentPet == nil { + return + } - our.CurPet[0].Info.HealPP(value) + currentPet.Info.HealPP(value) } func (our *Input) DelPP(value int) { + currentPet := our.CurrentPet() + if currentPet == nil { + return + } - for i := 0; i < len(our.CurPet[0].Info.SkillList); i++ { - if uint32(value) > our.CurPet[0].Info.SkillList[i].PP { - our.CurPet[0].Info.SkillList[i].PP = 0 + for i := 0; i < len(currentPet.Info.SkillList); i++ { + if uint32(value) > currentPet.Info.SkillList[i].PP { + currentPet.Info.SkillList[i].PP = 0 } else { - our.CurPet[0].Info.SkillList[i].PP -= uint32(value) + currentPet.Info.SkillList[i].PP -= uint32(value) } } @@ -102,6 +119,10 @@ func (our *Input) DelPP(value int) { // 这个方法是对对方造成伤害 // 伤害落实 // 血量扣减节点比如触发回神,反弹也在这里实现 func (our *Input) Damage(in *Input, sub *info.DamageZone) { + currentPet := our.CurrentPet() + if currentPet == nil { + return + } attacker := in if attacker == nil { attacker = our @@ -200,11 +221,11 @@ func (our *Input) Damage(in *Input, sub *info.DamageZone) { } - if uint32(sub.Damage.IntPart()) > our.CurPet[0].Info.Hp { + if uint32(sub.Damage.IntPart()) > currentPet.Info.Hp { - our.CurPet[0].Info.Hp = 0 + currentPet.Info.Hp = 0 } else { - our.CurPet[0].Info.Hp = our.CurPet[0].Info.Hp - uint32(sub.Damage.IntPart()) + currentPet.Info.Hp = currentPet.Info.Hp - uint32(sub.Damage.IntPart()) } //todo 待实现死亡effet @@ -213,9 +234,17 @@ func (our *Input) Damage(in *Input, sub *info.DamageZone) { // 计算技能威力 func (our *Input) CalculatePower(deftype *Input, skill *info.SkillEntity) alpacadecimal.Decimal { + if deftype == nil { + return alpacadecimal.Zero + } + ourPet := our.CurrentPet() + defPet := deftype.CurrentPet() + if ourPet == nil || defPet == nil { + return alpacadecimal.Zero + } // 1. 计算等级因子 (level * 0.4 + 2) - levelFactor := alpacadecimal.NewFromInt(int64(our.CurPet[0].Info.Level)). + levelFactor := alpacadecimal.NewFromInt(int64(ourPet.Info.Level)). Mul(alpacadecimal.NewFromFloat(0.4)).Add(alpacadecimal.NewFromInt(2)) var ( @@ -243,17 +272,17 @@ func (our *Input) CalculatePower(deftype *Input, skill *info.SkillEntity) alpaca // 11. DmgBindHpDv: 造成的伤害等于自身剩余体力*1/2+潜力(个体值); 默认: 0 if skill.XML.DmgBindLv != 0 { - skill.XML.Power = int(deftype.CurPet[0].Info.Level) + skill.XML.Power = int(defPet.Info.Level) } if skill.XML.PwrBindDv != 0 { if skill.XML.PwrBindDv == 1 { - skill.XML.Power = int(our.CurPet[0].Info.Dv * 5) + skill.XML.Power = int(ourPet.Info.Dv * 5) } if skill.XML.PwrBindDv == 2 { - skill.XML.Power = int(our.CurPet[0].Info.Hp/3 + our.CurPet[0].Info.Dv) + skill.XML.Power = int(ourPet.Info.Hp/3 + ourPet.Info.Dv) } } @@ -265,7 +294,7 @@ func (our *Input) CalculatePower(deftype *Input, skill *info.SkillEntity) alpaca } if skill.XML.DmgBindHpDv != 0 { - skill.XML.Power = int(our.CurPet[0].Info.Hp/2 + our.CurPet[0].Info.Dv) + skill.XML.Power = int(ourPet.Info.Hp/2 + ourPet.Info.Dv) } // 5. 基础伤害公式:等级因子 * 威力因子 * 攻击 / 防御 / 50 + 2 @@ -276,8 +305,8 @@ func (our *Input) CalculatePower(deftype *Input, skill *info.SkillEntity) alpaca Add(alpacadecimal.NewFromInt(2)) var typeRate alpacadecimal.Decimal - //fmt.Println(skill.Type().ID, deftype.CurPet[0].Type().ID) - t, _ := element.Calculator.GetOffensiveMultiplier(skill.GetType().ID, deftype.CurPet[0].GetType().ID) + //fmt.Println(skill.Type().ID, defPet.Type().ID) + t, _ := element.Calculator.GetOffensiveMultiplier(skill.GetType().ID, defPet.GetType().ID) our.AttackValue.Offensive = gconv.Float32(t) typeRate = alpacadecimal.NewFromFloat(t) diff --git a/logic/service/fight/input/input.go b/logic/service/fight/input/input.go index 334d09328..373515f66 100644 --- a/logic/service/fight/input/input.go +++ b/logic/service/fight/input/input.go @@ -20,6 +20,8 @@ type Input struct { // CanAction bool //是否可以行动 CurPet []*info.BattlePetEntity //当前上场精灵 AllPet []*info.BattlePetEntity + Team []*Input + OppTeam []*Input Player common.PlayerI Opp *Input CanCapture int @@ -30,8 +32,8 @@ type Input struct { EffectLost []Effect // 删掉伤害记录,可以在回调中记录,而不是每次调用记录 *model.AttackValue - FightC common.FightI - SumDamage alpacadecimal.Decimal //伤害 + FightC common.FightI + SumDamage alpacadecimal.Decimal //伤害 ShieldDamageTaken alpacadecimal.Decimal // 记录上一回合结束时的能力等级,供效果727等回溯使用。 LastTurnEndProp [6]int8 @@ -76,6 +78,63 @@ func (our *Input) PrimaryCurPet() *info.BattlePetEntity { return our.CurPetAt(0) } +// CurrentPet 返回“当前输入槽位”的出战精灵。 +// 注意:Input 本身已对应一个 actor 槽位,这里不是“整队的 0 号主位”。 +func (our *Input) CurrentPet() *info.BattlePetEntity { + return our.PrimaryCurPet() +} + +func (our *Input) IsCurrentPetCatchTime(catchTime uint32) bool { + current := our.CurrentPet() + if current == nil { + return false + } + return current.Info.CatchTime == catchTime +} + +func (our *Input) ControlledBy(userID uint32) bool { + return our != nil && our.Player != nil && our.Player.GetInfo().UserID == userID +} + +// BenchPets 返回当前站位后备精灵(不含当前出战精灵)。 +func (our *Input) BenchPets() []*info.BattlePetEntity { + if our == nil { + return nil + } + current := our.CurrentPet() + if current == nil { + return append([]*info.BattlePetEntity(nil), our.AllPet...) + } + bench := make([]*info.BattlePetEntity, 0, len(our.AllPet)) + for _, pet := range our.AllPet { + if pet == nil || pet.Info.CatchTime == current.Info.CatchTime { + continue + } + bench = append(bench, pet) + } + return bench +} + +func (our *Input) OpponentSlots() []*Input { + if our == nil { + return nil + } + if len(our.OppTeam) == 0 { + if our.Opp != nil { + return []*Input{our.Opp} + } + return nil + } + slots := make([]*Input, 0, len(our.OppTeam)) + for _, in := range our.OppTeam { + if in == nil { + continue + } + slots = append(slots, in) + } + return slots +} + func (our *Input) SetCurPetAt(index int, pet *info.BattlePetEntity) { if our == nil || index < 0 { return diff --git a/logic/service/fight/loop.go b/logic/service/fight/loop.go index a2405663f..044b340de 100644 --- a/logic/service/fight/loop.go +++ b/logic/service/fight/loop.go @@ -75,7 +75,8 @@ func (f *FightC) battleLoop() { if ff.Player.GetInfo().PetList[j].CatchTime == ff.AllPet[i].Info.CatchTime { if ff.UserID == f.WinnerId { - if ff.CurPet[0].Info.CatchTime == ff.Player.GetInfo().PetList[j].CatchTime { + currentPet := ff.CurrentPet() + if currentPet != nil && currentPet.Info.CatchTime == ff.Player.GetInfo().PetList[j].CatchTime { f.Winpet = &ff.Player.GetInfo().PetList[j] } @@ -100,9 +101,14 @@ func (f *FightC) battleLoop() { addpet.EffectInfo = nil //清空特性信息 f.Our[0].Player.(*player.Player).Service.Pet.PetAdd(&addpet, 0) + oppPet := f.Opp[0].CurrentPet() + petID := uint32(0) + if oppPet != nil { + petID = uint32(oppPet.ID) + } f.Our[0].Player.SendPackCmd(2409, &info.CatchMonsterOutboundInfo{ CatchTime: uint32(addpet.CatchTime), - PetId: uint32(f.Opp[0].CurPet[0].ID), + PetId: petID, }) defer f.Our[0].Player.(*player.Player).Service.Done.UpdatePet(addpet, 0, 1) @@ -323,11 +329,6 @@ func (f *FightC) handleTimeout(expectedSlots map[actionSlotKey]struct{}, actions } -type roundActionPair struct { - our action.BattleActionI - opp action.BattleActionI -} - func flattenActionMap(actions map[actionSlotKey]action.BattleActionI) []action.BattleActionI { flattened := make([]action.BattleActionI, 0, len(actions)) for _, act := range actions { @@ -371,44 +372,38 @@ func (f *FightC) sortActions(actions []action.BattleActionI) { }) } -func (f *FightC) buildRoundActionPairs(actions []action.BattleActionI) []roundActionPair { - remaining := append([]action.BattleActionI(nil), actions...) - f.sortActions(remaining) +func sortActionsByActor(actions []action.BattleActionI) { + sort.SliceStable(actions, func(i, j int) bool { + a, b := actions[i], actions[j] + if a == nil || b == nil { + return a != nil + } + if a.GetActorIndex() != b.GetActorIndex() { + return a.GetActorIndex() < b.GetActorIndex() + } + if a.GetTargetIndex() != b.GetTargetIndex() { + return a.GetTargetIndex() < b.GetTargetIndex() + } + return a.GetPlayerID() < b.GetPlayerID() + }) +} - used := make([]bool, len(remaining)) - pairs := make([]roundActionPair, 0, len(remaining)) - for i, act := range remaining { - if used[i] || act == nil { +func (f *FightC) splitRoundActions(actions []action.BattleActionI) ([]action.BattleActionI, []action.BattleActionI) { + our := make([]action.BattleActionI, 0, len(actions)) + opp := make([]action.BattleActionI, 0, len(actions)) + for _, act := range actions { + if act == nil { continue } - used[i] = true - - pair := roundActionPair{} if f.isOurPlayerID(act.GetPlayerID()) { - pair.our = act + our = append(our, act) } else { - pair.opp = act + opp = append(opp, act) } - - for j := i + 1; j < len(remaining); j++ { - candidate := remaining[j] - if used[j] || candidate == nil { - continue - } - if f.isOurPlayerID(candidate.GetPlayerID()) == f.isOurPlayerID(act.GetPlayerID()) { - continue - } - used[j] = true - if pair.our == nil { - pair.our = candidate - } else { - pair.opp = candidate - } - break - } - pairs = append(pairs, pair) } - return pairs + sortActionsByActor(our) + sortActionsByActor(opp) + return our, opp } // 根据动作类型执行一回合结算 @@ -418,11 +413,24 @@ func (f *FightC) resolveRound(actions []action.BattleActionI) { return } - for _, pair := range f.buildRoundActionPairs(actions) { + ourActions, oppActions := f.splitRoundActions(actions) + roundLen := len(ourActions) + if len(oppActions) > roundLen { + roundLen = len(oppActions) + } + for i := 0; i < roundLen; i++ { if f.closefight { return } - f.resolveActionPair(pair.our, pair.opp) + var ourAct action.BattleActionI + var oppAct action.BattleActionI + if i < len(ourActions) { + ourAct = ourActions[i] + } + if i < len(oppActions) { + oppAct = oppActions[i] + } + f.resolveActionPair(ourAct, oppAct) } } diff --git a/logic/service/fight/new.go b/logic/service/fight/new.go index bbfa50548..b8d0f8d01 100644 --- a/logic/service/fight/new.go +++ b/logic/service/fight/new.go @@ -56,6 +56,7 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) { } f.bindInputFightContext(f.Our) f.bindInputFightContext(f.Opp) + f.linkTeamViews() f.linkOppInputs() f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.primaryOur()) diff --git a/logic/service/fight/node/node.go b/logic/service/fight/node/node.go index 3e09d9792..07ca808fb 100644 --- a/logic/service/fight/node/node.go +++ b/logic/service/fight/node/node.go @@ -2,6 +2,7 @@ package node import ( element "blazing/common/data/Element" + "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" "sync" @@ -75,12 +76,90 @@ func (e *EffectNode) OpponentInput() *input.Input { return e.Ctx().Opp } +func (e *EffectNode) SourcePet() *info.BattlePetEntity { + in := e.SourceInput() + if in == nil { + return nil + } + return in.CurrentPet() +} + +func (e *EffectNode) CarrierPet() *info.BattlePetEntity { + in := e.CarrierInput() + if in == nil { + return nil + } + return in.CurrentPet() +} + +func (e *EffectNode) TargetPet() *info.BattlePetEntity { + in := e.TargetInput() + if in == nil { + return nil + } + return in.CurrentPet() +} + +func (e *EffectNode) OpponentPet() *info.BattlePetEntity { + in := e.OpponentInput() + if in == nil { + return nil + } + return in.CurrentPet() +} + +func (e *EffectNode) ForEachOpponentSlot(fn func(*input.Input) bool) { + if fn == nil { + return + } + carrier := e.CarrierInput() + if carrier == nil { + if opp := e.OpponentInput(); opp != nil { + fn(opp) + } + return + } + opponents := carrier.OpponentSlots() + if len(opponents) == 0 { + if opp := e.OpponentInput(); opp != nil { + fn(opp) + } + return + } + for _, opp := range opponents { + if opp == nil { + continue + } + if !fn(opp) { + return + } + } +} + +func (e *EffectNode) ForEachCarrierBenchPet(fn func(*info.BattlePetEntity) bool) { + if fn == nil { + return + } + carrier := e.CarrierInput() + if carrier == nil { + return + } + for _, pet := range carrier.BenchPets() { + if pet == nil { + continue + } + if !fn(pet) { + return + } + } +} + // IsOwner reports whether the current phase's Our side owns this effect. func (e *EffectNode) IsOwner() bool { - if e.Ctx().Our == nil || len(e.Ctx().Our.CurPet) == 0 || e.Ctx().Our.CurPet[0] == nil { + if e.Ctx().Our == nil { return false } - return e.ID().GetCatchTime() == e.Ctx().Our.CurPet[0].Info.CatchTime + return e.Ctx().Our.IsCurrentPetCatchTime(e.ID().GetCatchTime()) } func (e *EffectNode) Ctx() *input.Ctx { @@ -176,10 +255,15 @@ func (e *EffectNode) PropBefer(in *input.Input, prop int8, level int8) bool { func (e *EffectNode) ISNaturalEnemy() bool { source := e.SourceInput() opp := e.OpponentInput() - if source == nil || opp == nil || source.CurPet[0] == nil || opp.CurPet[0] == nil { + if source == nil || opp == nil { return false } - t, _ := element.Calculator.GetOffensiveMultiplier(opp.CurPet[0].Type, source.CurPet[0].Type) + sourcePet := source.CurrentPet() + oppPet := opp.CurrentPet() + if sourcePet == nil || oppPet == nil { + return false + } + t, _ := element.Calculator.GetOffensiveMultiplier(oppPet.Type, sourcePet.Type) if t <= 1 { return false diff --git a/logic/service/fight/node/skill.go b/logic/service/fight/node/skill.go index 691896e06..50c7310ad 100644 --- a/logic/service/fight/node/skill.go +++ b/logic/service/fight/node/skill.go @@ -38,8 +38,11 @@ func (e *EffectNode) OnSkill() bool { } func (e *EffectNode) Skill_Can() bool { - - return e.Input.CurPet[0].HP != 0 + currentPet := e.Input.CurrentPet() + if currentPet == nil { + return false + } + return currentPet.HP != 0 } func (e *EffectNode) GenSub(t input.Effect, Duration int) input.Effect { diff --git a/logic/service/fight/slot_view.go b/logic/service/fight/slot_view.go new file mode 100644 index 000000000..ffc4d34c8 --- /dev/null +++ b/logic/service/fight/slot_view.go @@ -0,0 +1,51 @@ +package fight + +import "blazing/logic/service/fight/input" + +const ( + SideOur = 1 + SideOpp = 2 +) + +type SlotView struct { + Side int + SlotIndex int + ControllerUserID uint32 + Input *input.Input +} + +func (f *FightC) SlotInput(side int, slotIndex int) *input.Input { + switch side { + case SideOur: + return f.selectInput(f.Our, slotIndex) + case SideOpp: + return f.selectInput(f.Opp, slotIndex) + default: + return nil + } +} + +func (f *FightC) SideSlots(side int) []SlotView { + var sideInputs []*input.Input + switch side { + case SideOur: + sideInputs = f.Our + case SideOpp: + sideInputs = f.Opp + default: + return nil + } + slots := make([]SlotView, 0, len(sideInputs)) + for idx, in := range sideInputs { + if in == nil || in.Player == nil { + continue + } + slots = append(slots, SlotView{ + Side: side, + SlotIndex: idx, + ControllerUserID: in.Player.GetInfo().UserID, + Input: in, + }) + } + return slots +}