refactor: 重构战斗系统支持多单位多动作

This commit is contained in:
xinian
2026-04-04 05:44:02 +08:00
committed by cnb
parent b62b4af628
commit 28d92c1e18
23 changed files with 1652 additions and 311 deletions

View File

@@ -1,36 +0,0 @@
# Task 202: Effects 1625-1629
## 目标
- 补齐以下 5 或最后一组不足 5 当前判定未实现的 skill effect
- 实现位置优先放在 `logic/service/fight/effect/`
- effect 需要展示说明同步更新 `logic/service/fight/effect/effect_info_map.go`
- 完成后至少执行`cd /workspace/logic && go test ./service/fight/effect`
## Effect 列表
### Effect 1625
- `argsNum`: `3`
- `info`: `造成的伤害高于{0}则{1}%令自身全属性+{2}`
### Effect 1626
- `argsNum`: `1`
- `info`: `后出手时将当回合护盾所承受的伤害值以百分比伤害的形式{0}%反弹给对手`
### Effect 1627
- `argsNum`: `3`
- `info`: `{0}回合做{1}-{2}次攻击,若本回合攻击次数达到最大则必定秒杀对手`
### Effect 1628
- `argsNum`: `2`
- `info`: `每次使用该技能击败对手则恢复自身全部体力,同时重置该技能使用次数并使该技能攻击威力提升{0}点,未击败对手时令自身下回合攻击技能先制+{1}`
### Effect 1629
- `argsNum`: `4`
- `info`: `{0}基础速度值{1}{2}则自身下回合先制+{3}`
- `param`: `4,0,0|7,1,1`
## 备注
- 该清单按当前仓库静态注册结果生成如果某个 effect 实际通过其他模块或运行时路径实现需要先复核后再落代码
- `201``445` 这类占位 effect优先补核心逻辑或补充明确的不可实现说明

View File

@@ -1,35 +0,0 @@
# Task 204: Effects 1635-1639
## 目标
- 补齐以下 5 或最后一组不足 5 当前判定未实现的 skill effect
- 实现位置优先放在 `logic/service/fight/effect/`
- effect 需要展示说明同步更新 `logic/service/fight/effect/effect_info_map.go`
- 完成后至少执行`cd /workspace/logic && go test ./service/fight/effect`
## Effect 列表
### Effect 1635
- `argsNum`: `2`
- `info`: `立刻恢复自身{0}点体力,{1}回合后恢复自身全部体力`
### Effect 1636
- `argsNum`: `0`
- `info`: `涵双1回合释放4-8张玫瑰卡牌进行攻击每张卡牌额外附加50点固定伤害自身体力低于最大体力的1/3时效果翻倍`
### Effect 1637
- `argsNum`: `2`
- `info`: `{0}回合内若对手使用属性技能,则使用属性技能后的下{1}回合属性技能命中效果失效`
### Effect 1638
- `argsNum`: `2`
- `info`: `{0}回合内若自身未受到攻击伤害则令对手全属性-{1}`
### Effect 1639
- `argsNum`: `0`
- `info`: `自身处于能力提升状态时100%打出致命一击`
## 备注
- 该清单按当前仓库静态注册结果生成如果某个 effect 实际通过其他模块或运行时路径实现需要先复核后再落代码
- `201``445` 这类占位 effect优先补核心逻辑或补充明确的不可实现说明

View File

@@ -1,35 +0,0 @@
# Task 206: Effects 1645-1649
## 目标
- 补齐以下 5 或最后一组不足 5 当前判定未实现的 skill effect
- 实现位置优先放在 `logic/service/fight/effect/`
- effect 需要展示说明同步更新 `logic/service/fight/effect/effect_info_map.go`
- 完成后至少执行`cd /workspace/logic && go test ./service/fight/effect`
## Effect 列表
### Effect 1645
- `argsNum`: `3`
- `info`: `{0}回合内对手使用属性技能则自身下{1}次受到的攻击伤害减少{2}%`
### Effect 1646
- `argsNum`: `1`
- `info`: `全属性+{0},对手存在致命裂痕时强化效果翻倍`
### Effect 1647
- `argsNum`: `4`
- `info`: `{0}回合内每回合使用技能吸取对手最大体力的1/{1}吸取体力时若自身体力低于最大体力的1/{2}则吸取效果翻倍,对手免疫百分比伤害时额外附加{3}点真实伤害`
### Effect 1648
- `argsNum`: `1`
- `info`: `附加自身最大体力{0}%的百分比伤害并恢复等量体力值,对手存在致命裂痕时转变为等量的真实伤害`
### Effect 1649
- `argsNum`: `4`
- `info`: `{0}%概率造成的攻击伤害为{1}倍对手每存在1层致命裂痕则概率提升{2}%,未触发则{3}回合内令对手使用的属性技能无效`
## 备注
- 该清单按当前仓库静态注册结果生成如果某个 effect 实际通过其他模块或运行时路径实现需要先复核后再落代码
- `201``445` 这类占位 effect优先补核心逻辑或补充明确的不可实现说明

View File

@@ -1,35 +0,0 @@
# Task 207: Effects 1650-1654
## 目标
- 补齐以下 5 或最后一组不足 5 当前判定未实现的 skill effect
- 实现位置优先放在 `logic/service/fight/effect/`
- effect 需要展示说明同步更新 `logic/service/fight/effect/effect_info_map.go`
- 完成后至少执行`cd /workspace/logic && go test ./service/fight/effect`
## Effect 列表
### Effect 1650
- `argsNum`: `4`
- `info`: `命中后{0}%随机为对手任意技能散布{1}枚致命印记,若对手当前精灵致命裂痕≥{2}层则额外散布{3}枚致命印记`
### Effect 1651
- `argsNum`: `2`
- `info`: `当回合击败对手则令对手下{0}次触发致命印记真实伤害效果转变为1/{1}`
### Effect 1652
- `argsNum`: `2`
- `info`: `释放技能时自身每损失{0}%的体力值则此技能威力提升{1}点`
### Effect 1653
- `argsNum`: `2`
- `info`: `释放技能时对手每残留{0}%的体力值则此技能附加{1}点固定伤害`
### Effect 1654
- `argsNum`: `1`
- `info`: `当回合击败对手则令对手下只登场精灵首次使用的技能所附加的效果失效`
## 备注
- 该清单按当前仓库静态注册结果生成如果某个 effect 实际通过其他模块或运行时路径实现需要先复核后再落代码
- `201``445` 这类占位 effect优先补核心逻辑或补充明确的不可实现说明

View File

@@ -1,36 +0,0 @@
# Task 208: Effects 1655-1659
## 目标
- 补齐以下 5 或最后一组不足 5 当前判定未实现的 skill effect
- 实现位置优先放在 `logic/service/fight/effect/`
- effect 需要展示说明同步更新 `logic/service/fight/effect/effect_info_map.go`
- 完成后至少执行`cd /workspace/logic && go test ./service/fight/effect`
## Effect 列表
### Effect 1655
- `argsNum`: `3`
- `info`: `{0}回合内每回合结束后{1}恢复自身所有技能{2}点PP值`
- `param`: `25,1,1`
### Effect 1656
- `argsNum`: `0`
- `info`: `100%复制对手当回合释放的技能若对手当回合使用的技能为攻击技能则令对手随机1个技能PP值归零若对手当回合使用的技能为属性技能则令对手下回合先制-2`
### Effect 1657
- `argsNum`: `3`
- `info`: `己方每有一只精灵死亡则附加{0}点固定伤害对手体力高于最大体力的1/{1}时转变为{2}点`
### Effect 1658
- `argsNum`: `0`
- `info`: `3回合内每回合80%闪避对手攻击未触发时自身处于圣念状态则使对手随机1项技能PP值归零自身处于邪念状态则使对手失明`
### Effect 1659
- `argsNum`: `4`
- `info`: `随机附加给对手{0}-{1}点固定伤害,若打出致命一击则效果转变为吸取对手{2}-{3}点体力`
## 备注
- 该清单按当前仓库静态注册结果生成如果某个 effect 实际通过其他模块或运行时路径实现需要先复核后再落代码
- `201``445` 这类占位 effect优先补核心逻辑或补充明确的不可实现说明

View File

@@ -1,5 +1,23 @@
# 战斗多单位模式改造交接文档2026-04-04
## 0. 2026-04-04 本轮完成情况
本轮已完成以下落地项
- 动作提交改为按 `playerID + actorIndex` 去重同一玩家同回合的多个上场位动作不会再互相覆盖
- 主循环已从双动作入口改为动作列表入口`resolveRound` 现接收并处理 `[]action.BattleActionI`
- 回合结算增加了基于优先级和速度的统一排序并按跨阵营动作对子顺序执行保留现有 `enterturn(first, second)` 兼容层
- 技能和道具的目标选择已接入 `targetIndex`不再固定打对面 `0` 号位
- 切宠同步改为携带 `actorIndex`同一玩家多上场位的切宠播报不再冲突
- 开战同步结构新增当前战斗位数组同时保留 `Info1/Info2` 兼容旧结构
- `FightI` 已补充 `UseSkillAt/ChangePetAt/UseItemAt/GetCurrPETAt`
- 增加了动作队列的基础单测覆盖同玩家不同槽位保留同槽位动作替换
本轮仍保留的限制
- `enterturn` 和大量 `effect/node` 逻辑仍是双动作上下文因此当前实现采用动作列表排序 + 跨阵营配对兼容执行的过渡方案而不是一次性重写所有效果系统
- `NewFight` 仍按现有建房流程创建双方 1 个战斗位本轮打通的是多战斗位结算骨架和接口不是外部建房入口的全量切换
## 1. 任务目标
将当前战斗系统从每回合双方各 1 个动作的模型改造成支持多上场位多操作者的统一回合模型最终支持以下 3 种战斗模式
@@ -280,4 +298,3 @@
- 当前仓库工作区可能是脏的不要回滚无关修改
- 这次改造的真正核心不是结构字段改数组而是把回合系统从双动作模型改成动作列表模型
- 已有 `ActorIndex/TargetIndex` 只是入口铺垫不代表多单位模式已经完成

View File

@@ -8,15 +8,19 @@ import (
type FightI interface {
Over(c PlayerI, id model.EnumBattleOverReason) //逃跑
UseSkill(c PlayerI, id uint32) //使用技能
GetCurrPET(c PlayerI) *info.BattlePetEntity //当前精灵
UseSkillAt(c PlayerI, id uint32, actorIndex, targetIndex int)
GetCurrPET(c PlayerI) *info.BattlePetEntity //当前精灵
GetCurrPETAt(c PlayerI, actorIndex int) *info.BattlePetEntity
GetOverInfo() model.FightOverInfo
Ownerid() uint32
ReadyFight(c PlayerI) //是否准备战斗
ChangePet(c PlayerI, id uint32)
ChangePetAt(c PlayerI, id uint32, actorIndex int)
Capture(c PlayerI, id uint32)
LoadPercent(c PlayerI, percent int32)
UseItem(c PlayerI, cacthid, itemid uint32)
UseItemAt(c PlayerI, cacthid, itemid uint32, actorIndex, targetIndex int)
Chat(c PlayerI, msg string)
IsFirst(c PlayerI) bool
GetOverChan() chan struct{}

View File

@@ -6,12 +6,16 @@ import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/player/model"
"github.com/jinzhu/copier"
)
// Compare 比较两个1v1战斗动作的执行优先级(核心逻辑)
func (*FightC) Compare(a, b action.BattleActionI) (action.BattleActionI, action.BattleActionI) {
// Compare 比较两个战斗动作的执行优先级
func (f *FightC) Compare(a, b action.BattleActionI) (action.BattleActionI, action.BattleActionI) {
if a == nil {
return b, a
}
if b == nil {
return a, b
}
// 动作本身的优先级比较
p1 := b.Priority() - a.Priority()
if p1 > 0 { // 对手优先级更高
@@ -20,11 +24,22 @@ func (*FightC) Compare(a, b action.BattleActionI) (action.BattleActionI, action.
return a, b
}
if speedA, speedB := f.actionSpeed(a), f.actionSpeed(b); speedA.Cmp(speedB) != 0 {
if speedA.Cmp(speedB) > 0 {
return a, b
}
return b, a
}
if a.GetActorIndex() != b.GetActorIndex() {
if a.GetActorIndex() < b.GetActorIndex() {
return a, b
}
return b, a
}
return a, b // 速度相同时,发起方优先
}
const maxPendingActionsPerPlayer = 2
func (f *FightC) openActionWindow() {
f.actionMu.Lock()
f.acceptActions = true
@@ -58,16 +73,15 @@ func (f *FightC) submitAction(act action.BattleActionI) {
f.actionMu.Unlock()
return
}
count := 0
replaceIndex := -1
for i, pending := range f.pendingActions {
if pending == nil || pending.GetPlayerID() != act.GetPlayerID() {
if pending == nil || actionSlotKeyFromAction(pending) != actionSlotKeyFromAction(act) {
continue
}
count++
replaceIndex = i
break
}
if count >= maxPendingActionsPerPlayer && replaceIndex >= 0 {
if replaceIndex >= 0 {
f.pendingActions[replaceIndex] = act
} else {
f.pendingActions = append(f.pendingActions, act)
@@ -263,21 +277,51 @@ func (f *FightC) ReadyFight(c common.PlayerI) {
// buildFightStartInfo 构建战斗开始时需要发送给双方的信息
func (f *FightC) buildFightStartInfo() info.FightStartOutboundInfo {
var startInfo info.FightStartOutboundInfo
// 复制双方初始宠物信息(取列表第一个宠物)
if len(f.ReadyInfo.OurPetList) > 0 {
_ = copier.Copy(&startInfo.Info1, &f.ReadyInfo.OurPetList[0])
startInfo.Info1.UserID = f.ReadyInfo.OurInfo.UserID
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(f.ReadyInfo.OpponentPetList) > 0 {
_ = copier.Copy(&startInfo.Info2, &f.ReadyInfo.OpponentPetList[0])
startInfo.Info2.UserID = f.ReadyInfo.OpponentInfo.UserID
if len(oppInfos) > 0 {
startInfo.Info2 = oppInfos[0]
}
return startInfo
}
func (f *FightC) collectFightPetInfos(inputs []*input.Input) []info.FightPetInfo {
infos := make([]info.FightPetInfo, 0, len(inputs))
for actorIndex, fighter := range inputs {
if fighter == nil || fighter.Player == nil {
continue
}
currentPet := fighter.PrimaryCurPet()
if currentPet == nil {
continue
}
fightInfo := info.FightPetInfo{
UserID: fighter.Player.GetInfo().UserID,
ActorIndex: uint32(actorIndex),
ControllerUserID: currentPet.ControllerUserID,
ID: currentPet.Info.ID,
Name: currentPet.Info.Name,
CatchTime: currentPet.Info.CatchTime,
Hp: currentPet.Info.Hp,
MaxHp: currentPet.Info.MaxHp,
Level: currentPet.Info.Level,
Catchable: uint32(fighter.CanCapture),
}
if fighter.AttackValue != nil {
fightInfo.Prop = fighter.AttackValue.Prop
}
infos = append(infos, fightInfo)
}
return infos
}
// checkBothPlayersReady 检查PVP战斗中双方是否都已准备完成
// 参数c为当前准备的玩家返回true表示双方均准备完成
func (f *FightC) checkBothPlayersReady(currentPlayer common.PlayerI) bool {

View File

@@ -0,0 +1,45 @@
package fight
import (
"blazing/logic/service/fight/action"
"testing"
)
func TestSubmitActionKeepsDifferentActorSlots(t *testing.T) {
f := &FightC{
acceptActions: true,
pendingActions: make([]action.BattleActionI, 0, 4),
}
f.actionRound.Store(1)
first := &action.SelectSkillAction{BaseAction: action.BaseAction{PlayerID: 1001, ActorIndex: 0}}
second := &action.SelectSkillAction{BaseAction: action.BaseAction{PlayerID: 1001, ActorIndex: 1}}
f.submitAction(first)
f.submitAction(second)
if got := len(f.pendingActions); got != 2 {
t.Fatalf("expected 2 pending actions, got %d", got)
}
}
func TestSubmitActionReplacesSameActorSlot(t *testing.T) {
f := &FightC{
acceptActions: true,
pendingActions: make([]action.BattleActionI, 0, 4),
}
f.actionRound.Store(1)
first := &action.SelectSkillAction{BaseAction: action.BaseAction{PlayerID: 1001, ActorIndex: 0}}
replacement := &action.SelectSkillAction{BaseAction: action.BaseAction{PlayerID: 1001, ActorIndex: 0, TargetIndex: 1}}
f.submitAction(first)
f.submitAction(replacement)
if got := len(f.pendingActions); got != 1 {
t.Fatalf("expected 1 pending action, got %d", got)
}
if f.pendingActions[0] != replacement {
t.Fatalf("expected replacement action to be kept")
}
}

View File

@@ -0,0 +1,199 @@
package effect
import (
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/alpacahq/alpacadecimal"
"github.com/gogf/gf/v2/util/grand"
)
// Effect 1635: 立刻恢复自身{0}点体力,{1}回合后恢复自身全部体力
type Effect1635 struct {
node.EffectNode
}
func (e *Effect1635) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Our == nil {
return true
}
if e.Args()[0].Cmp(alpacadecimal.Zero) > 0 {
e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, e.Args()[0])
}
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1635, int(e.Args()[1].IntPart()))
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1635Sub struct {
RoundEffectArg0Base
}
func (e *Effect1635Sub) TurnEnd() {
if len(e.Args()) == 0 {
return
}
if e.Duration() == 1 && e.Ctx().Our != nil && e.Ctx().Our.CurPet[0] != nil {
e.Ctx().Our.Heal(
e.Ctx().Our,
&action.SelectSkillAction{},
e.Ctx().Our.CurPet[0].GetMaxHP().Sub(e.Ctx().Our.CurPet[0].GetHP()),
)
}
e.EffectNode.TurnEnd()
}
// Effect 1636: 涵双1回合释放4-8张玫瑰卡牌进行攻击每张卡牌额外附加50点固定伤害自身体力低于最大体力的1/3时效果翻倍
type Effect1636 struct {
node.EffectNode
hits int
bonusHP alpacadecimal.Decimal
multipl int
}
func (e *Effect1636) SkillHit() bool {
if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || e.Ctx().SkillEntity.AttackTime == 0 {
return true
}
base := 4 + grand.Intn(5)
extra := base
if e.Ctx().Our != nil && e.Ctx().Our.CurPet[0] != nil {
threshold := e.Ctx().Our.CurPet[0].GetMaxHP().Div(alpacadecimal.NewFromInt(3))
if e.Ctx().Our.CurPet[0].GetHP().Cmp(threshold) < 0 {
extra *= 2
}
}
e.hits = extra
if e.hits > 1 {
e.Ctx().SkillEntity.AttackTime += uint32(e.hits - 1)
}
e.bonusHP = alpacadecimal.NewFromInt(int64(extra * 50))
return true
}
func (e *Effect1636) DamageDivEx(zone *info.DamageZone) bool {
if e.hits <= 0 || zone == nil || zone.Type != info.DamageType.Red {
return true
}
if e.bonusHP.Cmp(alpacadecimal.Zero) > 0 {
zone.Damage = zone.Damage.Add(e.bonusHP)
}
e.hits = 0
return true
}
// Effect 1637: {0}回合内若对手使用属性技能,则使用属性技能后的下{1}回合属性技能命中效果失效
type Effect1637 struct {
node.EffectNode
triggered bool
}
func (e *Effect1637) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Opp == nil {
return true
}
sub := e.Ctx().Opp.InitEffect(input.EffectType.Sub, 1637, int(e.Args()[0].IntPart()), int(e.Args()[1].IntPart()))
if sub != nil {
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1637Sub struct {
RoundEffectArg0Base
applied bool
}
func (e *Effect1637Sub) ActionStart(fattack, sattack *action.SelectSkillAction) bool {
if e.applied || len(e.Args()) < 2 {
return true
}
oppAction := actionByPlayer(fattack, sattack, e.Ctx().Opp.UserID)
if oppAction == nil || oppAction.SkillEntity == nil || oppAction.SkillEntity.Category() != info.Category.STATUS {
return true
}
sub := e.Ctx().Opp.InitEffect(input.EffectType.Sub, 16371, int(e.Args()[1].IntPart()))
if sub != nil {
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
}
e.applied = true
return true
}
type Effect16371Sub struct {
RoundEffectArg0Base
}
func (e *Effect16371Sub) SkillHit_ex() bool {
if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() != info.Category.STATUS {
return true
}
e.Ctx().SkillEntity.SetNoSide()
e.Ctx().SkillEntity.AttackTime = 0
return true
}
// Effect 1638: {0}回合内若自身未受到攻击伤害则令对手全属性-{1}
type Effect1638 struct {
RoundEffectArg0Base
touched bool
}
func (e *Effect1638) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Our == nil {
return true
}
sub := e.Ctx().Our.InitEffect(
input.EffectType.Sub,
1638,
int(e.Args()[0].IntPart()),
int(e.Args()[1].IntPart()),
)
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
func (e *Effect1638) DamageSubEx(zone *info.DamageZone) bool {
if zone != nil && zone.Type == info.DamageType.Red && zone.Damage.Cmp(alpacadecimal.Zero) > 0 {
e.touched = true
}
return true
}
func (e *Effect1638) TurnEnd() {
if len(e.Args()) >= 2 && !e.touched {
applyAllPropDown(e.Ctx().Our, e.Ctx().Opp, int8(e.Args()[1].IntPart()))
}
e.touched = false
e.EffectNode.TurnEnd()
}
// Effect 1639: 自身处于能力提升状态时100%打出致命一击
type Effect1639 struct {
node.EffectNode
}
func (e *Effect1639) SkillHit() bool {
if e.Ctx().SkillEntity == nil || e.Ctx().Our == nil || !e.Ctx().Our.HasPropADD() || e.Ctx().SkillEntity.Category() == info.Category.STATUS {
return true
}
e.Ctx().SkillEntity.XML.CritRate = 16
return true
}
func init() {
input.InitEffect(input.EffectType.Skill, 1635, &Effect1635{})
input.InitEffect(input.EffectType.Sub, 1635, &Effect1635Sub{})
input.InitEffect(input.EffectType.Skill, 1636, &Effect1636{})
input.InitEffect(input.EffectType.Skill, 1637, &Effect1637{})
input.InitEffect(input.EffectType.Sub, 1637, &Effect1637Sub{})
input.InitEffect(input.EffectType.Sub, 16371, &Effect16371Sub{})
input.InitEffect(input.EffectType.Skill, 1638, &Effect1638{})
input.InitEffect(input.EffectType.Skill, 1639, &Effect1639{})
}

View File

@@ -0,0 +1,160 @@
package effect
import (
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/alpacahq/alpacadecimal"
)
func addFatalMarks(target *input.Input, count int) {
if target == nil || count <= 0 {
return
}
pet := target.CurPet[0]
if pet == nil {
return
}
pet.FatalCrackLayers += count
}
// Effect 1640: 出手时若自身满体力则100%打出致命一击
type Effect1640 struct{ node.EffectNode }
func (e *Effect1640) Skill_Use() bool {
if e.Ctx().Our == nil || e.Ctx().Opp == nil || e.Ctx().Our.CurPet[0] == nil {
return true
}
if e.Ctx().Our.CurPet[0].GetHP().Cmp(e.Ctx().Our.CurPet[0].GetMaxHP()) < 0 {
return true
}
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1640)
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1640Sub struct{ FixedDuration1Base }
func (e *Effect1640Sub) ActionStart(fattack, sattack *action.SelectSkillAction) bool {
current := actionByPlayer(fattack, sattack, e.Ctx().Our.UserID)
if current == nil || current.SkillEntity == nil || current.SkillEntity.Category() == info.Category.STATUS {
return true
}
current.SkillEntity.XML.CritRate = 16
return true
}
// Effect 1641: 自身处于能力提升状态时造成伤害的{0}%恢复自身体力值当前体力低于最大体力的1/{1}时附加等量百分比伤害
type Effect1641 struct{ node.EffectNode }
func (e *Effect1641) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Our == nil || e.Ctx().Opp == nil || e.Ctx().Our.CurPet[0] == nil || e.Ctx().Opp.CurPet[0] == nil {
return true
}
if !e.Ctx().Our.HasPropADD() {
return true
}
damage := e.Ctx().Our.SumDamage
if damage.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
heal := damage.Mul(e.Args()[0]).Div(hundred)
if heal.Cmp(alpacadecimal.Zero) > 0 {
e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, heal)
}
threshold := e.Ctx().Our.CurPet[0].GetMaxHP().Div(e.Args()[1])
if e.Ctx().Our.CurPet[0].GetHP().Cmp(threshold) < 0 && heal.Cmp(alpacadecimal.Zero) > 0 {
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.True, Damage: heal})
}
return true
}
// Effect 1642: 消除对手能力提升状态,消除成功则{0}%随机为对手任意技能散布{1}枚致命印记
type Effect1642 struct{ node.EffectNode }
func (e *Effect1642) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Opp == nil {
return true
}
cleared := clearPositiveProps(e.Ctx().Opp, e.Ctx().Our)
if !cleared {
return true
}
if ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100); ok {
addFatalMarks(e.Ctx().Opp, int(e.Args()[1].IntPart()))
}
return true
}
// Effect 1643: 对手每存在1层致命裂痕则附加{0}点真实伤害
type Effect1643 struct{ node.EffectNode }
func (e *Effect1643) Skill_Use() bool {
if len(e.Args()) == 0 || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil {
return true
}
layers := e.Ctx().Opp.CurPet[0].FatalCrackLayers
if layers <= 0 {
return true
}
damage := alpacadecimal.NewFromInt(int64(layers)).Mul(e.Args()[0])
if damage.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.True, Damage: damage})
return true
}
// Effect 1644: {0}回合内对手使用攻击技能则随机进入{1}种异常状态,未触发则随机为对手任意技能散布{2}枚致命印记
type Effect1644 struct{ node.EffectNode }
func (e *Effect1644) Skill_Use() bool {
if len(e.Args()) < 3 || e.Ctx().Opp == nil {
return true
}
sub := e.Ctx().Opp.InitEffect(input.EffectType.Sub, 1644, e.SideEffectArgs...)
if sub != nil {
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1644Sub struct {
RoundEffectArg0Base
triggered bool
}
func (e *Effect1644Sub) Action_end() bool {
if len(e.Args()) < 3 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || e.Ctx().SkillEntity.AttackTime == 0 {
return true
}
if e.Args()[1].IntPart() > 0 {
applyRandomStatuses1507(e.Ctx().Our, e.Ctx().Opp, int(e.Args()[1].IntPart()))
}
e.triggered = true
return true
}
func (e *Effect1644Sub) TurnEnd() {
if !e.triggered && len(e.Args()) >= 3 {
addFatalMarks(e.Ctx().Opp, int(e.Args()[2].IntPart()))
}
e.triggered = false
e.EffectNode.TurnEnd()
}
func init() {
input.InitEffect(input.EffectType.Skill, 1640, &Effect1640{})
input.InitEffect(input.EffectType.Sub, 1640, &Effect1640Sub{})
input.InitEffect(input.EffectType.Skill, 1641, &Effect1641{})
input.InitEffect(input.EffectType.Skill, 1642, &Effect1642{})
input.InitEffect(input.EffectType.Skill, 1643, &Effect1643{})
input.InitEffect(input.EffectType.Skill, 1644, &Effect1644{})
input.InitEffect(input.EffectType.Sub, 1644, &Effect1644Sub{})
}

View File

@@ -0,0 +1,272 @@
package effect
import (
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/alpacahq/alpacadecimal"
)
const fatalCrackEffectID = 17001
func fatalCrackLayers(target *input.Input) int {
if target == nil {
return 0
}
total := 0
for _, eff := range target.Effects {
if eff == nil || !eff.Alive() || eff.ID().GetEffectType() != input.EffectType.Sub {
continue
}
if int(eff.ID().Suffix()) != fatalCrackEffectID {
continue
}
if sub, ok := eff.(*FatalCrackSub); ok {
total += sub.layers
}
}
return total
}
func addFatalCrack(owner, target *input.Input, layers int) {
if target == nil || owner == nil || layers <= 0 {
return
}
eff := owner.InitEffect(input.EffectType.Sub, fatalCrackEffectID, layers)
if eff != nil {
target.AddEffect(owner, eff)
}
}
type FatalCrackSub struct {
node.EffectNode
layers int
}
func (f *FatalCrackSub) SetArgs(t *input.Input, a ...int) {
f.EffectNode.SetArgs(t, a...)
f.CanStack(true)
f.Duration(-1)
if len(a) > 0 {
f.layers = a[0]
}
if f.layers <= 0 {
f.layers = 0
}
}
// Effect 1645: {0}回合内对手使用属性技能则自身下{1}次受到的攻击伤害减少{2}%
type Effect1645 struct{ node.EffectNode }
func (e *Effect1645) Skill_Use() bool {
if len(e.Args()) < 3 {
return true
}
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1645, int(e.Args()[0].IntPart()), int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart()))
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1645Sub struct {
RoundEffectArg0Base
active bool
hits int
reduce alpacadecimal.Decimal
}
func (e *Effect1645Sub) SetArgs(t *input.Input, a ...int) {
e.RoundEffectArg0Base.SetArgs(t, a...)
if len(a) > 1 {
e.hits = a[1]
}
if len(a) > 2 {
e.reduce = alpacadecimal.NewFromInt(int64(a[2]))
}
if e.hits < 0 {
e.hits = 0
}
if e.reduce.Cmp(alpacadecimal.Zero) < 0 {
e.reduce = alpacadecimal.Zero
}
e.active = false
}
func (e *Effect1645Sub) SkillHit_ex() bool {
if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() != info.Category.STATUS {
return true
}
if e.hits <= 0 {
return true
}
e.active = true
return true
}
func (e *Effect1645Sub) DamageDivEx(zone *info.DamageZone) bool {
if !e.active || e.hits <= 0 || zone == nil || zone.Type != info.DamageType.Red || e.reduce.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
zone.Damage = zone.Damage.Mul(hundred.Sub(e.reduce)).Div(hundred)
e.hits--
if e.hits <= 0 {
e.active = false
}
return true
}
// Effect 1646: 全属性+{0},对手存在致命裂痕时强化效果翻倍
type Effect1646 struct{ node.EffectNode }
func (e *Effect1646) Skill_Use() bool {
if len(e.Args()) == 0 {
return true
}
boost := int8(e.Args()[0].IntPart())
if fatalCrackLayers(e.Ctx().Opp) > 0 {
boost *= 2
}
for i := 0; i < 6; i++ {
e.Ctx().Our.SetProp(e.Ctx().Our, int8(i), boost)
}
return true
}
// Effect 1647: {0}回合内每回合使用技能吸取对手最大体力的1/{1}吸取体力时若自身体力低于最大体力的1/{2}则吸取效果翻倍,对手免疫百分比伤害时额外附加{3}点真实伤害
type Effect1647 struct{ node.EffectNode }
func (e *Effect1647) Skill_Use() bool {
if len(e.Args()) < 4 {
return true
}
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1647, e.SideEffectArgs...)
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1647Sub struct{ RoundEffectArg0Base }
func (e *Effect1647Sub) OnSkill() bool {
if len(e.Args()) < 4 || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil {
return true
}
drain := e.Ctx().Opp.CurPet[0].GetMaxHP().Div(e.Args()[1])
threshold := e.Ctx().Our.CurPet[0].GetMaxHP().Div(alpacadecimal.NewFromInt(2))
if e.Ctx().Our.CurPet[0].GetHP().Cmp(threshold) < 0 {
drain = drain.Mul(alpacadecimal.NewFromInt(2))
}
if drain.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
before := e.Ctx().Opp.CurPet[0].GetHP()
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Percent, Damage: drain})
e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, drain)
if e.Ctx().Opp.CurPet[0].GetHP().Cmp(before) == 0 && e.Args()[3].Cmp(alpacadecimal.Zero) > 0 {
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.True, Damage: e.Args()[3]})
}
return true
}
// Effect 1648: 附加自身最大体力{0}%的百分比伤害并恢复等量体力值,对手存在致命裂痕时转变为等量的真实伤害
type Effect1648 struct{ node.EffectNode }
func (e *Effect1648) OnSkill() bool {
if len(e.Args()) == 0 || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil {
return true
}
percent := e.Args()[0]
damage := e.Ctx().Our.CurPet[0].GetMaxHP().Mul(percent).Div(alpacadecimal.NewFromInt(100))
if damage.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
target := &info.DamageZone{Type: info.DamageType.Percent, Damage: damage}
if fatalCrackLayers(e.Ctx().Opp) > 0 {
target.Type = info.DamageType.True
}
e.Ctx().Opp.Damage(e.Ctx().Our, target)
e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, damage)
return true
}
// Effect 1649: {0}%概率造成的攻击伤害为{1}倍对手每存在1层致命裂痕则概率提升{2}%,未触发则{3}回合内令对手使用的属性技能无效
type Effect1649 struct{ node.EffectNode }
func (e *Effect1649) Skill_Use() bool {
if len(e.Args()) < 4 {
return true
}
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1649, e.SideEffectArgs...)
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1649Sub struct {
RoundEffectArg0Base
fired bool
}
func (e *Effect1649Sub) SkillHit() bool {
e.fired = false
return true
}
func (e *Effect1649Sub) Damage_Mul(zone *info.DamageZone) bool {
if e.fired || zone == nil || zone.Type != info.DamageType.Red || len(e.Args()) < 4 {
return true
}
chance := int(e.Args()[0].IntPart())
chance += fatalCrackLayers(e.Ctx().Opp) * int(e.Args()[2].IntPart())
if chance > 100 {
chance = 100
}
if chance > 0 {
if ok, _, _ := e.Input.Player.Roll(chance, 100); ok {
mul := int(e.Args()[1].IntPart())
if mul <= 1 {
mul = 1
}
e.fired = true
zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(int64(mul)))
return true
}
}
duration := int(e.Args()[3].IntPart())
if duration > 0 {
block := e.Ctx().Opp.InitEffect(input.EffectType.Sub, 16494, duration)
if block != nil {
e.Ctx().Opp.AddEffect(e.Ctx().Our, block)
}
}
e.fired = true
return true
}
type Effect1649BlockSub struct{ RoundEffectArg0Base }
func (e *Effect1649BlockSub) SkillHit_ex() bool {
if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() != info.Category.STATUS {
return true
}
e.Ctx().SkillEntity.SetNoSide()
return true
}
func init() {
input.InitEffect(input.EffectType.Sub, fatalCrackEffectID, &FatalCrackSub{})
input.InitEffect(input.EffectType.Skill, 1645, &Effect1645{})
input.InitEffect(input.EffectType.Sub, 1645, &Effect1645Sub{})
input.InitEffect(input.EffectType.Skill, 1646, &Effect1646{})
input.InitEffect(input.EffectType.Skill, 1647, &Effect1647{})
input.InitEffect(input.EffectType.Sub, 1647, &Effect1647Sub{})
input.InitEffect(input.EffectType.Skill, 1648, &Effect1648{})
input.InitEffect(input.EffectType.Skill, 1649, &Effect1649{})
input.InitEffect(input.EffectType.Sub, 1649, &Effect1649Sub{})
input.InitEffect(input.EffectType.Sub, 16494, &Effect1649BlockSub{})
}

View File

@@ -0,0 +1,270 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/alpacahq/alpacadecimal"
"github.com/gogf/gf/v2/util/grand"
)
const fatalMarkDamagePercent = 10
func randomSkillIndexes(count int, skills []*info.SkillEntity) []int {
if count <= 0 || len(skills) == 0 {
return nil
}
if count > len(skills) {
count = len(skills)
}
indexes := grand.Perm(len(skills))
return indexes[:count]
}
func addFatalMark(owner, target *input.Input, skillID int) bool {
if owner == nil || target == nil {
return false
}
sub := target.InitEffect(input.EffectType.Sub, 1650, skillID)
if sub == nil {
return false
}
target.AddEffect(owner, sub)
return true
}
func fatalMarkCount(target *input.Input) int {
if target == nil {
return 0
}
count := 0
for _, eff := range target.Effects {
if eff == nil || !eff.Alive() {
continue
}
if eff.ID().GetEffectType() != input.EffectType.Sub || int(eff.ID().Suffix()) != 1650 {
continue
}
count++
}
return count
}
func fatalMarkReduction(target *input.Input) (int, *Effect1651Sub) {
if target == nil {
return 1, nil
}
eff := target.GetEffect(input.EffectType.Sub, 1651)
if eff == nil {
return 1, nil
}
sub, ok := eff.(*Effect1651Sub)
if !ok || sub.remaining <= 0 {
return 1, nil
}
divisor := sub.divisor
if divisor <= 0 {
divisor = 1
}
sub.remaining--
if sub.remaining <= 0 {
sub.Alive(false)
}
return divisor, sub
}
// Effect 1650: 命中后{0}%随机为对手任意技能散布{1}枚致命印记,若对手当前精灵致命裂痕≥{2}层则额外散布{3}枚致命印记
type Effect1650 struct {
node.EffectNode
}
func (e *Effect1650) SkillHit() bool {
if len(e.Args()) < 4 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || e.Ctx().SkillEntity.AttackTime == 0 || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil {
return true
}
if ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100); !ok {
return true
}
count := int(e.Args()[1].IntPart())
if count <= 0 {
return true
}
skillList := e.Ctx().Opp.CurPet[0].Skills
selected := randomSkillIndexes(count, skillList)
if fatalMarkCount(e.Ctx().Opp) >= int(e.Args()[2].IntPart()) {
extra := randomSkillIndexes(int(e.Args()[3].IntPart()), skillList)
selected = append(selected, extra...)
}
for _, idx := range selected {
if idx < 0 || idx >= len(skillList) {
continue
}
addFatalMark(e.Ctx().Our, e.Ctx().Opp, skillList[idx].XML.ID)
}
return true
}
type Effect1650MarkSub struct {
node.EffectNode
skillID int
}
func (e *Effect1650MarkSub) SetArgs(t *input.Input, a ...int) {
e.EffectNode.SetArgs(t, a...)
e.Duration(-1)
if len(a) > 0 {
e.skillID = a[0]
}
}
func (e *Effect1650MarkSub) SkillHit() bool {
if e.skillID == 0 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.AttackTime == 0 || e.Ctx().SkillEntity.Category() == info.Category.STATUS {
return true
}
if e.Ctx().SkillEntity.XML.ID != e.skillID {
return true
}
damage := e.Ctx().Our.CurPet[0].GetMaxHP().Mul(alpacadecimal.NewFromInt(fatalMarkDamagePercent)).Div(alpacadecimal.NewFromInt(100))
divisor, _ := fatalMarkReduction(e.Ctx().Opp)
damage = damage.Div(alpacadecimal.NewFromInt(int64(divisor)))
if damage.Cmp(alpacadecimal.Zero) > 0 {
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.True, Damage: damage})
}
e.Alive(false)
return true
}
// Effect 1651: 当回合击败对手则令对手下{0}次触发致命印记真实伤害效果转变为1/{1}
type Effect1651 struct {
node.EffectNode
}
func (e *Effect1651) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil || e.Ctx().Opp.CurPet[0].Info.Hp > 0 {
return true
}
sub := e.Ctx().Opp.InitEffect(input.EffectType.Sub, 1651, int(e.Args()[0].IntPart()), int(e.Args()[1].IntPart()))
if sub != nil {
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1651Sub struct {
node.EffectNode
remaining int
divisor int
}
func (e *Effect1651Sub) SetArgs(t *input.Input, a ...int) {
e.EffectNode.SetArgs(t, a...)
e.Duration(-1)
if len(a) > 0 {
e.remaining = a[0]
}
if len(a) > 1 {
e.divisor = a[1]
}
}
// Effect 1652: 释放技能时自身每损失{0}%的体力值则此技能威力提升{1}点
type Effect1652 struct {
node.EffectNode
}
func (e *Effect1652) SkillHit() bool {
if len(e.Args()) < 2 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil {
return true
}
maxHP := e.Ctx().Our.CurPet[0].GetMaxHP()
curHP := e.Ctx().Our.CurPet[0].GetHP()
if maxHP.Cmp(alpacadecimal.Zero) == 0 {
return true
}
missing := maxHP.Sub(curHP)
percent := missing.Mul(alpacadecimal.NewFromInt(100)).Div(maxHP)
unit := e.Args()[0]
if unit.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
steps := int(percent.Div(unit).IntPart())
if steps <= 0 {
return true
}
e.Ctx().SkillEntity.XML.Power += steps * int(e.Args()[1].IntPart())
return true
}
// Effect 1653: 释放技能时对手每残留{0}%的体力值则此技能附加{1}点固定伤害
type Effect1653 struct {
node.EffectNode
}
func (e *Effect1653) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.AttackTime == 0 {
return true
}
maxHP := e.Ctx().Opp.CurPet[0].GetMaxHP()
curHP := e.Ctx().Opp.CurPet[0].GetHP()
if maxHP.Cmp(alpacadecimal.Zero) == 0 {
return true
}
percent := curHP.Mul(alpacadecimal.NewFromInt(100)).Div(maxHP)
unit := e.Args()[0]
if unit.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
count := int(percent.Div(unit).IntPart())
if count <= 0 {
return true
}
damage := alpacadecimal.NewFromInt(int64(count) * int64(e.Args()[1].IntPart()))
if damage.Cmp(alpacadecimal.Zero) > 0 {
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: damage})
}
return true
}
// Effect 1654: 当回合击败对手则令对手下只登场精灵首次使用的技能所附加的效果失效
type Effect1654 struct {
node.EffectNode
}
func (e *Effect1654) Skill_Use() bool {
if len(e.Args()) == 0 || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil || e.Ctx().Opp.CurPet[0].Info.Hp > 0 {
return true
}
sub := e.Ctx().Opp.InitEffect(input.EffectType.Sub, 1654)
if sub != nil {
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1654Sub struct {
node.EffectNode
deployed bool
}
func (e *Effect1654Sub) OnSkill() bool {
if e.deployed || e.Ctx().SkillEntity == nil {
return true
}
e.Ctx().SkillEntity.XML.SideEffectS = nil
e.Ctx().SkillEntity.XML.SideEffectArgS = nil
e.deployed = true
e.Alive(false)
return true
}
func init() {
input.InitEffect(input.EffectType.Skill, 1650, &Effect1650{})
input.InitEffect(input.EffectType.Sub, 1650, &Effect1650MarkSub{})
input.InitEffect(input.EffectType.Skill, 1651, &Effect1651{})
input.InitEffect(input.EffectType.Sub, 1651, &Effect1651Sub{})
input.InitEffect(input.EffectType.Skill, 1652, &Effect1652{})
input.InitEffect(input.EffectType.Skill, 1653, &Effect1653{})
input.InitEffect(input.EffectType.Skill, 1654, &Effect1654{})
input.InitEffect(input.EffectType.Sub, 1654, &Effect1654Sub{})
}

View File

@@ -0,0 +1,233 @@
package effect
import (
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/alpacahq/alpacadecimal"
"github.com/gogf/gf/v2/util/grand"
)
// Effect 1655: {0}回合内每回合结束后{1}%恢复自身所有技能{2}点PP值
type Effect1655 struct{ node.EffectNode }
func (e *Effect1655) Skill_Use() bool {
if len(e.Args()) < 3 {
return true
}
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1655, int(e.Args()[0].IntPart()), int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart()))
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1655Sub struct{ RoundEffectArg0Base }
func (e *Effect1655Sub) TurnEnd() {
if len(e.Args()) < 3 || e.Ctx().Our == nil || e.Args()[2].Cmp(alpacadecimal.Zero) <= 0 {
e.EffectNode.TurnEnd()
return
}
chance := int(e.Args()[1].IntPart())
if chance <= 0 {
e.EffectNode.TurnEnd()
return
}
if ok, _, _ := e.Input.Player.Roll(chance, 100); ok {
amount := int(e.Args()[2].IntPart())
if amount > 0 && e.Ctx().Our.CurPet[0] != nil {
for i := range e.Ctx().Our.CurPet[0].Info.SkillList {
e.Ctx().Our.CurPet[0].Info.SkillList[i].PP += uint32(amount)
}
}
}
e.EffectNode.TurnEnd()
}
// Effect 1656: 100%复制对手当回合释放的技能若对手当回合使用的技能为攻击技能则令对手随机1个技能PP值归零若对手当回合使用的技能为属性技能则令对手下回合先制-2
type Effect1656 struct {
node.EffectNode
applied bool
}
func (e *Effect1656) ComparePre(fattack, sattack *action.SelectSkillAction) bool {
if e.applied {
return true
}
current := actionByPlayer(fattack, sattack, e.Ctx().Our.UserID)
opponent := actionByPlayer(fattack, sattack, e.Ctx().Opp.UserID)
if current == nil || opponent == nil || opponent.SkillEntity == nil {
return true
}
clone := cloneSkillEntity(opponent.SkillEntity)
if clone == nil {
return true
}
current.SkillEntity = clone
e.applied = true
if opponent.SkillEntity.Category() != info.Category.STATUS {
zeroRandomSkillPP(e.Ctx().Opp, 1)
} else {
sub := e.Ctx().Opp.InitEffect(input.EffectType.Sub, 1656, 1, 2)
if sub != nil {
e.Ctx().Opp.AddEffect(e.Ctx().Opp, sub)
}
}
return true
}
func (e *Effect1656) Action_end() bool {
e.applied = false
return true
}
type Effect1656Sub struct {
FixedDuration1Base
priority int
}
func (e *Effect1656Sub) SetArgs(t *input.Input, a ...int) {
e.FixedDuration1Base.SetArgs(t, a...)
e.CanStack(false)
if len(a) > 1 {
e.priority = a[1]
}
}
func (e *Effect1656Sub) ComparePre(fattack, sattack *action.SelectSkillAction) bool {
if e.priority == 0 {
return true
}
current := actionByPlayer(fattack, sattack, e.Ctx().Opp.UserID)
if current == nil || current.SkillEntity == nil {
return true
}
current.SkillEntity.XML.Priority -= e.priority
return true
}
// Effect 1657: 己方每有一只精灵死亡则附加{0}点固定伤害对手体力高于最大体力的1/{1}时转变为{2}点
type Effect1657 struct{ node.EffectNode }
func (e *Effect1657) Skill_Use() bool {
if len(e.Args()) < 3 || e.Ctx().Opp == nil || e.Ctx().Our == nil {
return true
}
dead := 0
for _, pet := range e.Ctx().Our.AllPet {
if pet == nil {
continue
}
if !pet.Alive() {
dead++
}
}
if dead == 0 {
return true
}
damage := e.Args()[0].Mul(alpacadecimal.NewFromInt(int64(dead)))
if e.Args()[1].Cmp(alpacadecimal.Zero) > 0 && e.Ctx().Opp.CurPet[0] != nil {
threshold := e.Ctx().Opp.CurPet[0].GetMaxHP().Div(e.Args()[1])
if threshold.Cmp(alpacadecimal.Zero) > 0 && e.Ctx().Opp.CurPet[0].GetHP().Cmp(threshold) > 0 {
damage = e.Args()[2]
}
}
if damage.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: damage})
return true
}
// Effect 1658: 3回合内每回合80%闪避对手攻击未触发时自身处于圣念状态则使对手随机1项技能PP值归零自身处于邪念状态则使对手失明
type Effect1658 struct{ node.EffectNode }
func (e *Effect1658) Skill_Use() bool {
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1658, 3, 80)
if sub != nil {
e.Ctx().Our.AddEffect(e.Ctx().Our, sub)
}
return true
}
type Effect1658Sub struct{ RoundEffectArg0Base }
func (e *Effect1658Sub) SkillHit_ex() bool {
if len(e.Args()) < 2 || e.Ctx().Opp == nil || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || e.Ctx().SkillEntity.AttackTime == 0 {
return true
}
if e.Ctx().SkillEntity.AttackTime == 2 {
return true
}
chance := int(e.Args()[1].IntPart())
if chance <= 0 {
return true
}
if ok, _, _ := e.Input.Player.Roll(chance, 100); ok {
e.Ctx().SkillEntity.SetMiss()
return true
}
switch {
case e.Ctx().Our.StatEffect_Exist(petStatus2077Holy):
zeroRandomSkillPP(e.Ctx().Opp, 1)
case e.Ctx().Our.StatEffect_Exist(petStatus2077Evil):
addStatusByID(e.Ctx().Our, e.Ctx().Opp, int(info.PetStatus.Blind))
}
return true
}
// Effect 1659: 随机附加给对手{0}-{1}点固定伤害,若打出致命一击则效果转变为吸取对手{2}-{3}点体力
type Effect1659 struct{ node.EffectNode }
func (e *Effect1659) Skill_Use() bool {
if len(e.Args()) < 4 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.AttackTime == 0 || e.Ctx().Opp == nil {
return true
}
minDamage := int(e.Args()[0].IntPart())
maxDamage := int(e.Args()[1].IntPart())
if maxDamage < minDamage {
maxDamage = minDamage
}
damage := alpacadecimal.NewFromInt(int64(minDamage))
if maxDamage > minDamage {
damage = alpacadecimal.NewFromInt(int64(minDamage + grand.Intn(maxDamage-minDamage+1)))
}
if e.Ctx().SkillEntity.Crit != 0 {
minDrain := int(e.Args()[2].IntPart())
maxDrain := int(e.Args()[3].IntPart())
if maxDrain < minDrain {
maxDrain = minDrain
}
if minDrain < 0 {
minDrain = 0
}
drain := minDrain
if maxDrain > minDrain {
drain += grand.Intn(maxDrain - minDrain + 1)
}
if drain > 0 {
amount := alpacadecimal.NewFromInt(int64(drain))
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: amount})
e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, amount)
}
return true
}
if damage.Cmp(alpacadecimal.Zero) > 0 {
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: damage})
}
return true
}
func init() {
input.InitEffect(input.EffectType.Skill, 1655, &Effect1655{})
input.InitEffect(input.EffectType.Sub, 1655, &Effect1655Sub{})
input.InitEffect(input.EffectType.Skill, 1656, &Effect1656{})
input.InitEffect(input.EffectType.Sub, 1656, &Effect1656Sub{})
input.InitEffect(input.EffectType.Skill, 1657, &Effect1657{})
input.InitEffect(input.EffectType.Skill, 1658, &Effect1658{})
input.InitEffect(input.EffectType.Sub, 1658, &Effect1658Sub{})
input.InitEffect(input.EffectType.Skill, 1659, &Effect1659{})
}

View File

@@ -18,6 +18,7 @@ func (e *Effect63) OnSkill() bool {
for i, v := range e.Ctx().Our.Prop[:] {
if v < 0 {
e.Ctx().Opp.SetProp(e.Ctx().Our, int8(i), v)
e.Ctx().Our.SetProp(e.Ctx().Our, int8(i), 0)
}
}

View File

@@ -1060,6 +1060,26 @@ var effectInfoByID = map[int]string{
1632: "吸取对手{0}点体力若对手任意1项技能PP值小于{1}点则额外吸取{2}点体力",
1633: "使自身体力百分比与对手体力百分比对调,自身体力百分比高于对手时{0}%令对手{1}",
1634: "自身体力低于250时必定先手",
1640: "出手时若自身满体力则100%打出致命一击",
1641: "自身处于能力提升状态时造成伤害的{0}%恢复自身体力值当前体力低于最大体力的1/{1}时附加等量百分比伤害",
1642: "消除对手能力提升状态,消除成功则{0}%随机在对手身上散布{1}枚致命印记",
1643: "对手每存在1层致命裂痕则附加{0}点真实伤害",
1644: "{0}回合内对手使用攻击技能则随机进入{1}种异常状态,未触发则随机散布{2}枚致命印记",
1645: "{0}回合内对手使用属性技能则自身下{1}次受到的攻击伤害减少{2}%",
1646: "全属性+{0},对手存在致命裂痕时强化效果翻倍",
1647: "{0}回合内每回合使用技能吸取对手最大体力的1/{1}吸取体力时若自身体力低于最大体力的1/{2}则吸取效果翻倍,对手免疫百分比伤害时额外附加{3}点真实伤害",
1648: "附加自身最大体力{0}%的百分比伤害并恢复等量体力值,对手存在致命裂痕时转变为等量的真实伤害",
1649: "{0}%概率造成的攻击伤害为{1}倍对手每存在1层致命裂痕则概率提升{2}%,未触发则{3}回合内令对手使用的属性技能无效",
1650: "命中后{0}%随机为对手任意技能散布{1}枚致命印记,若对手当前精灵致命裂痕≥{2}层则额外散布{3}枚致命印记",
1651: "当回合击败对手则令对手下{0}次触发致命印记真实伤害效果转变为1/{1}",
1652: "释放技能时自身每损失{0}%的体力值则此技能威力提升{1}点",
1653: "释放技能时对手每残留{0}%的体力值则此技能附加{1}点固定伤害",
1654: "当回合击败对手则令对手下只登场精灵首次使用的技能所附加的效果失效",
1655: "{0}回合内每回合结束后{1}%恢复自身所有技能{2}点PP值",
1656: "100%复制对手当回合释放的技能若对手当回合使用的技能为攻击技能则令对手随机1个技能PP值归零若对手当回合使用的技能为属性技能则令对手下回合先制-2",
1657: "己方每有一只精灵死亡则附加{0}点固定伤害对手体力高于最大体力的1/{1}时转变为{2}点",
1658: "3回合内每回合80%闪避对手攻击未触发时自身处于圣念状态则使对手随机1项技能PP值归零自身处于邪念状态则使对手失明",
1659: "随机附加给对手{0}-{1}点固定伤害,若打出致命一击则效果转变为吸取对手{2}-{3}点体力",
1601: "命中后附加自身最大体力{0}%的百分比伤害,若打出的攻击伤害为奇数则额外恢复等量体力值",
1602: "{0}回合内每回合使用技能恢复自身最大体力的1/{1},恢复体力时若自身为满体力则恢复己方所有不在场精灵{2}点体力",
1603: "{0}%降低对手所有PP值{1}点",
@@ -1088,6 +1108,11 @@ var effectInfoByID = map[int]string{
1627: "{0}回合做{1}-{2}次攻击,若本回合攻击次数达到最大则必定秒杀对手",
1628: "每次使用该技能击败对手则恢复自身全部体力,同时重置该技能使用次数并使该技能攻击威力提升{0}点,未击败对手时令自身下回合攻击技能先制+{1}",
1629: "{0}基础速度值{1}{2}则自身下回合先制+{3}",
1635: "立刻恢复自身{0}点体力,{1}回合后恢复自身全部体力",
1636: "涵双1回合释放4-8张玫瑰卡牌进行攻击每张卡牌额外附加50点固定伤害自身体力低于最大体力的1/3时效果翻倍",
1637: "{0}回合内若对手使用属性技能,则使用属性技能后的下{1}回合属性技能命中效果失效",
1638: "{0}回合内若自身未受到攻击伤害则令对手全属性-{1}",
1639: "自身处于能力提升状态时100%打出致命一击",
1670: "{0}%令对手{1},对手为自身天敌时概率提升{2}%,未触发则消除对手回合类效果",
1671: "造成的攻击伤害不低于{0},若对手处于能力提升状态则造成的攻击伤害不低于{1}",
1672: "出手时若自身未满体力则吸取对手{0}点体力",

View File

@@ -16,6 +16,9 @@ import (
// processSkillAttack 处理技能攻击逻辑
func (f *FightC) processSkillAttack(attacker, defender *input.Input, skill *info.SkillEntity) {
if attacker == nil || defender == nil || skill == nil {
return
}
skill.AttackTimeC(attacker.Prop[5]) //计算命中
defender.Exec(func(effect input.Effect) bool { //计算闪避,然后修改对方命中),同时相当于计算属性无效这种
@@ -33,8 +36,8 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, skill *info
var originalProps [2][6]int8
var originalPetInfo [2]model.PetInfo
//复制属性
originalProps[0], originalProps[1] = f.Our[0].Prop, f.Opp[0].Prop //先复制能力提升
originalPetInfo[0], originalPetInfo[1] = f.Our[0].CurPet[0].Info, f.Opp[0].CurPet[0].Info //先复制宠物信息
originalProps[0], originalProps[1] = attacker.Prop, defender.Prop
originalPetInfo[0], originalPetInfo[1] = attacker.CurPet[0].Info, defender.CurPet[0].Info
attacker.Exec(func(effect input.Effect) bool {
//计算变威力
effect.Ctx().SkillEntity = skill
@@ -55,8 +58,8 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, skill *info
}
//还原属性
f.Our[0].Prop, f.Opp[0].Prop = originalProps[0], originalProps[1]
f.Our[0].CurPet[0].Info, f.Opp[0].CurPet[0].Info = originalPetInfo[0], originalPetInfo[1]
attacker.Prop, defender.Prop = originalProps[0], originalProps[1]
attacker.CurPet[0].Info, defender.CurPet[0].Info = originalPetInfo[0], originalPetInfo[1]
if attacker.IsCritical == 1 { //命中了才有暴击
//暴击破防
@@ -149,15 +152,13 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
})
})
if firstAttack != nil { //如果首技能是空的,说明都空过了
firstAttacker, _ := f.getSkillParticipants(firstAttack)
if firstAttacker != nil {
firstAttacker.Parseskill(firstAttack)
}
secondAttacker, _ := f.getSkillParticipants(secondAttack)
if secondAttacker != nil {
secondAttacker.Parseskill(secondAttack)
for _, skillAction := range []*action.SelectSkillAction{firstAttack, secondAttack} {
attackerInput, _ := f.getSkillParticipants(skillAction)
if attackerInput == nil {
continue
}
f.setActionAttackValue(skillAction)
attackerInput.Parseskill(skillAction)
}
f.Broadcast(func(fighter *input.Input) {
fighter.Exec(func(effect input.Effect) bool { //回合开始前
@@ -212,17 +213,26 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
for i := 0; i < 2; i++ {
var originalSkill *info.SkillEntity //原始技能
var currentSkill *info.SkillEntity //当前技能
var currentAction *action.SelectSkillAction
if i == 0 {
attacker, defender = f.First, f.Second
currentAction = firstAttack
attacker, defender = f.getSkillParticipants(firstAttack)
originalSkill = f.copySkill(firstAttack)
//先手阶段,先修复后手效果
f.Second.RecoverEffect()
} else {
attacker, defender = f.Second, f.First
currentAction = secondAttack
attacker, defender = f.getSkillParticipants(secondAttack)
originalSkill = f.copySkill(secondAttack)
//取消后手历史效果
f.Second.ReactvieEffect()
}
if attacker == nil {
attacker = f.First
}
if defender == nil {
defender = f.Second
}
currentSkill = originalSkill
defender.Exec(func(effect input.Effect) bool { //这个是能否使用技能
@@ -234,7 +244,7 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
return effect.ActionStart(firstAttack, secondAttack)
})
canUse := canUseSkill && action.CanUse(currentSkill) && attacker.CurPet[0].Info.Hp > 0
canUse := canUseSkill && action.CanUse(currentSkill) && attacker != nil && attacker.CurPet[0].Info.Hp > 0
if !canUse {
@@ -250,6 +260,7 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
//先手权不一定出手
} else {
f.setActionAttackValue(currentAction)
for _, effect := range attacker.EffectCache {
effect.IsFirst(true)
@@ -345,7 +356,7 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
}
}
})
f.Switch = make(map[uint32]*action.ActiveSwitchAction)
f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction)
if f.closefight && f.Info.Mode == info.BattleMode.PET_MELEE {
// f.Broadcast(func(fighter *input.Input) {
// if fighter.UserID != f.WinnerId {

View File

@@ -17,6 +17,7 @@ type BattlePetEntity struct {
Shield alpacadecimal.Decimal
DivineEnergy int
ControllerUserID uint32
FatalCrackLayers int
//*input.Input
//PType int

View File

@@ -9,6 +9,8 @@ import (
type ChangePetInfo struct {
// UserId 米米号野怪为0
UserId uint32 `json:"userId"`
// ActorIndex 当前发生切宠的战斗位
ActorIndex uint32 `json:"actorIndex"`
// PetId 切换上场的精灵编号
ID uint32 `fieldDesc:"当前对战精灵ID" `
// PetName 精灵名字固定16字节长度
@@ -75,6 +77,10 @@ type RPCFightStartinfo struct {
type FightPetInfo struct {
// 用户ID野怪为0
UserID uint32 `fieldDesc:"用户ID 野怪为0" `
// ActorIndex 当前战斗位下标
ActorIndex uint32 `json:"actorIndex"`
// ControllerUserID 当前战斗位绑定的操作者
ControllerUserID uint32 `json:"controllerUserId"`
// 当前对战精灵ID
ID uint32 `fieldDesc:"当前对战精灵ID" `
@@ -104,20 +110,21 @@ type AttackValueS struct {
func NewAttackValue(userid uint32) *model.AttackValue {
return &model.AttackValue{
userid,
0,
0,
0,
0,
0,
0,
0,
0,
[]model.SkillInfo{},
0,
[20]int8{},
[6]int8{},
0,
UserID: userid,
ActorIndex: 0,
TargetIndex: 0,
SkillID: 0,
AttackTime: 0,
LostHp: 0,
GainHp: 0,
RemainHp: 0,
MaxHp: 0,
State: 0,
SkillList: []model.SkillInfo{},
IsCritical: 0,
Status: [20]int8{},
Prop: [6]int8{},
Offensive: 0,
}
}
@@ -200,6 +207,9 @@ type NoteUseSkillOutboundInfo struct {
type FightStartOutboundInfo struct {
IsCanAuto uint32 `fieldDesc:"是否自动 默认给0 怀疑是自动战斗器使用的" `
InfoLen uint32 `struc:"sizeof=Infos"`
Infos []FightPetInfo
// 当前战斗精灵信息1前端通过userid判断是否为我方
Info1 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"`

View File

@@ -30,14 +30,14 @@ type FightC struct {
Opp []*input.Input // 敌方战斗位
OurPlayers []common.PlayerI // 我方操作者
OppPlayers []common.PlayerI // 敌方操作者
Switch map[uint32]*action.ActiveSwitchAction
Switch map[actionSlotKey]*action.ActiveSwitchAction
startl sync.Once
StartTime time.Time
actionMu sync.Mutex
actionNotify chan struct{}
acceptActions bool
pendingActions []action.BattleActionI // 待处理动作队列,同一玩家最多保留两段动作
pendingActions []action.BattleActionI // 待处理动作队列,同一战斗位最多保留一个动作
actionRound atomic.Uint32
quit chan struct{}
@@ -53,6 +53,25 @@ type FightC struct {
callback func(model.FightOverInfo)
}
type actionSlotKey struct {
PlayerID uint32
ActorIndex int
}
func newActionSlotKey(playerID uint32, actorIndex int) actionSlotKey {
return actionSlotKey{
PlayerID: playerID,
ActorIndex: actorIndex,
}
}
func actionSlotKeyFromAction(act action.BattleActionI) actionSlotKey {
if act == nil {
return actionSlotKey{}
}
return newActionSlotKey(act.GetPlayerID(), act.GetActorIndex())
}
func (f *FightC) primaryOur() *input.Input {
if len(f.Our) == 0 {
return nil
@@ -116,6 +135,20 @@ func (f *FightC) isOurPlayerID(userID uint32) bool {
return userID == f.ownerID
}
func (f *FightC) getSideInputs(userID uint32, isOpposite bool) []*input.Input {
isOur := f.isOurPlayerID(userID)
if isOpposite {
if isOur {
return f.Opp
}
return f.Our
}
if isOur {
return f.Our
}
return f.Opp
}
func (f *FightC) findInputByUserID(userID uint32) (*input.Input, bool) {
isOur := f.isOurPlayerID(userID)
if isOur {
@@ -131,17 +164,36 @@ func (f *FightC) findInputByUserID(userID uint32) (*input.Input, bool) {
}
func (f *FightC) getInputByUserID(userID uint32, index int, isOpposite bool) *input.Input {
isOur := f.isOurPlayerID(userID)
if isOpposite {
if isOur {
return f.selectInput(f.Opp, index)
return f.selectInput(f.getSideInputs(userID, isOpposite), index)
}
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
}
return f.selectInput(f.Our, index)
slots[newActionSlotKey(fighter.Player.GetInfo().UserID, actorIndex)] = struct{}{}
}
if isOur {
return f.selectInput(f.Our, index)
for actorIndex, fighter := range f.Opp {
if fighter == nil || fighter.Player == nil {
continue
}
slots[newActionSlotKey(fighter.Player.GetInfo().UserID, actorIndex)] = struct{}{}
}
return f.selectInput(f.Opp, index)
return slots
}
func (f *FightC) setActionAttackValue(act action.BattleActionI) {
if act == nil {
return
}
attacker := f.GetInputByAction(act, false)
if attacker == nil || attacker.AttackValue == nil {
return
}
attacker.AttackValue.ActorIndex = uint32(act.GetActorIndex())
attacker.AttackValue.TargetIndex = uint32(act.GetTargetIndex())
}
func (f *FightC) Ownerid() uint32 {
@@ -174,7 +226,14 @@ func (f *FightC) GetInputByAction(c action.BattleActionI, isOpposite bool) *inpu
// 玩家使用技能
func (f *FightC) GetCurrPET(c common.PlayerI) *info.BattlePetEntity {
in := f.GetInputByPlayer(c, false)
return f.GetCurrPETAt(c, 0)
}
func (f *FightC) GetCurrPETAt(c common.PlayerI, actorIndex int) *info.BattlePetEntity {
if c == nil {
return nil
}
in := f.getInputByUserID(c.GetInfo().UserID, actorIndex, false)
if in == nil {
return nil
}
@@ -293,10 +352,17 @@ func initfightready(in *input.Input) (model.FightUserInfo, []model.ReadyFightPet
// 被击败的ID
func (f *FightC) IsWin(c *input.Input) bool {
for _, v := range f.GetInputByPlayer(c.Player, true).AllPet {
if v.Alive() { //如果存活
return false
if c == nil || c.Player == nil {
return false
}
for _, sideInput := range f.getSideInputs(c.Player.GetInfo().UserID, true) {
if sideInput == nil {
continue
}
for _, v := range sideInput.AllPet {
if v.Alive() {
return false
}
}
}
return true

View File

@@ -7,6 +7,7 @@ import (
"blazing/cool"
"blazing/modules/player/model"
"context"
"sort"
"sync/atomic"
"blazing/logic/service/common"
@@ -45,20 +46,19 @@ func (f *FightC) battleLoop() {
}()
//fmt.Println("战斗开始精灵", f.Our[0].Player.GetInfo().PetList[0].CatchTime)
ourID := f.Our[0].Player.GetInfo().UserID
oppID := f.Opp[0].Player.GetInfo().UserID
//fmt.Println("开始收集玩家动作", waitr)
for !f.closefight {
f.Round++
actions := f.collectPlayerActions(ourID, oppID)
expectedSlots := f.expectedActionSlots()
actions := f.collectPlayerActions(expectedSlots)
if f.closefight {
break
}
f.resolveRound(actions[ourID], actions[oppID])
f.resolveRound(actions)
}
f.Broadcast(func(ff *input.Input) {
@@ -145,8 +145,8 @@ func (f *FightC) battleLoop() {
}
// 收集玩家动作(含超时判定)
func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.BattleActionI {
actions := make(map[uint32]action.BattleActionI)
func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{}) []action.BattleActionI {
actions := make(map[actionSlotKey]action.BattleActionI, len(expectedSlots))
f.openActionWindow()
defer f.closeActionWindow()
@@ -157,12 +157,12 @@ func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.Bat
waitr := time.Duration(f.waittime)*time.Millisecond*10 + 30*time.Second
timeout := time.After(waitr)
for len(actions) < 2 {
for len(actions) < len(expectedSlots) {
select {
case <-f.quit:
f.closefight = true
return actions
return flattenActionMap(actions)
case <-f.actionNotify:
paction := f.nextAction()
@@ -170,18 +170,21 @@ func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.Bat
continue
}
pid := paction.GetPlayerID()
if pid != ourID && pid != oppID {
key := actionSlotKeyFromAction(paction)
if _, ok := expectedSlots[key]; !ok {
continue
}
selfinput := f.GetInputByAction(paction, false)
if selfinput == nil {
continue
}
if ret, ok := paction.(*action.ActiveSwitchAction); ok {
//正常结束可以切换,以及死切后还能再切一次
if selfinput.CanChange == 0 {
if selfinput.CurPet[0].Info.Hp > 0 { //非死亡切换
currentPet := selfinput.PrimaryCurPet()
if currentPet != nil && currentPet.Info.Hp > 0 { //非死亡切换
selfinput.CanChange = 1
f.Broadcast(func(ff *input.Input) {
@@ -199,11 +202,17 @@ func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.Bat
}
// oldpet := selfinput.CurPet[0]
// InitAttackValue := *selfinput.AttackValue
selfinput.CurPet[0], ret.Reason = selfinput.GetPet(ret.Cid)
nextPet, reason := selfinput.GetPet(ret.Cid)
if nextPet == nil {
continue
}
selfinput.SetCurPetAt(0, nextPet)
ret.Reason = reason
ret.Reason.ActorIndex = uint32(ret.ActorIndex)
selfinput.Player.SendPackCmd(2407, &ret.Reason)
f.Switch[selfinput.UserID] = ret
f.Switch[key] = ret
selfinput.InitAttackValue() //切换精灵消除能力提升
//这时候精灵已经切换过了,可以直接给新精灵加效果
@@ -220,8 +229,8 @@ func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.Bat
if selfinput.CanChange == 2 {
selfinput.CanChange = 0
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC && pid == 0 {
f.Switch = make(map[uint32]*action.ActiveSwitchAction)
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC && paction.GetPlayerID() == 0 {
f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction)
f.Our[0].Player.SendPackCmd(2407, &ret.Reason)
//println("AI出手死切")
go f.Opp[0].GetAction() //boss出手后获取出招
@@ -235,51 +244,78 @@ func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.Bat
} else {
// println("玩家执行释放技能动作:", pid, paction.(*action.SelectSkillAction).Info.ID)
if selfinput.CurPet[0].Info.Hp <= 0 { //0血执行非切换动作
currentPet := selfinput.PrimaryCurPet()
if currentPet == nil || currentPet.Info.Hp <= 0 { //0血执行非切换动作
//todo 记录异常操作
cool.Logger.Print(context.TODO(), "玩家执行了异常操作当前精灵血量为0不能执行非切换动作", pid)
cool.Logger.Print(context.TODO(), "玩家执行了异常操作当前精灵血量为0不能执行非切换动作", paction.GetPlayerID())
continue
}
if selfinput.CanChange == 1 { //非被动死亡情况下,不能执行额外动作,0允许切2是死亡可以额外动作
cool.Logger.Print(context.TODO(), "玩家执行了异常操作,切换后二次释放技能,不能执行非切换动作", pid)
cool.Logger.Print(context.TODO(), "玩家执行了异常操作,切换后二次释放技能,不能执行非切换动作", paction.GetPlayerID())
continue
}
}
actions[pid] = paction
actions[key] = paction
//fmt.Println("玩家执行动作:", pid, paction.Priority())
case <-timeout:
r := f.handleTimeout(ourID, oppID, actions)
r := f.handleTimeout(expectedSlots, actions)
if r {
return actions
return flattenActionMap(actions)
}
}
}
return actions
return flattenActionMap(actions)
}
// 超时处理逻辑
func (f *FightC) handleTimeout(ourID, oppID uint32, actions map[uint32]action.BattleActionI) bool {
func (f *FightC) handleTimeout(expectedSlots map[actionSlotKey]struct{}, actions map[actionSlotKey]action.BattleActionI) bool {
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
// f.WinnerId = 0
go f.UseSkill(f.Our[0].Player, 0) //boss出手后获取出招
for key := range expectedSlots {
if _, exists := actions[key]; exists || !f.isOurPlayerID(key.PlayerID) {
continue
}
player := f.getPlayerByID(key.PlayerID)
if player != nil {
go f.UseSkillAt(player, 0, key.ActorIndex, 0)
}
}
return false
} else {
var pid uint32
for _, pid = range []uint32{ourID, oppID} {
if _, exists := actions[pid]; exists {
missingOur := false
missingOpp := false
for key := range expectedSlots {
if _, exists := actions[key]; exists {
continue
}
if f.isOurPlayerID(key.PlayerID) {
missingOur = true
continue
}
missingOpp = true
}
switch {
case missingOur && !missingOpp:
if player := f.primaryOppPlayer(); player != nil {
f.WinnerId = player.GetInfo().UserID
}
case missingOpp && !missingOur:
if player := f.primaryOurPlayer(); player != nil {
f.WinnerId = player.GetInfo().UserID
}
default:
for _, act := range actions {
f.WinnerId = act.GetPlayerID()
break
}
if f.WinnerId == 0 {
f.WinnerId = f.ownerID
}
}
//获胜方是已经出招的
f.WinnerId = f.GetInputByPlayer(f.getPlayerByID(pid), false).Player.GetInfo().UserID
f.Reason = model.BattleOverReason.PlayerOVerTime
f.closefight = true
return true
@@ -287,10 +323,111 @@ func (f *FightC) handleTimeout(ourID, oppID uint32, actions map[uint32]action.Ba
}
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 {
if act != nil {
flattened = append(flattened, act)
}
}
return flattened
}
func (f *FightC) actionSpeed(act action.BattleActionI) alpacadecimal.Decimal {
if act == nil {
return alpacadecimal.Zero
}
attacker := f.GetInputByAction(act, false)
if attacker == nil {
return alpacadecimal.Zero
}
return attacker.GetProp(4)
}
func (f *FightC) sortActions(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.Priority() != b.Priority() {
return a.Priority() > b.Priority()
}
if speedA, speedB := f.actionSpeed(a), f.actionSpeed(b); speedA.Cmp(speedB) != 0 {
return speedA.Cmp(speedB) > 0
}
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()
})
}
func (f *FightC) buildRoundActionPairs(actions []action.BattleActionI) []roundActionPair {
remaining := append([]action.BattleActionI(nil), actions...)
f.sortActions(remaining)
used := make([]bool, len(remaining))
pairs := make([]roundActionPair, 0, len(remaining))
for i, act := range remaining {
if used[i] || act == nil {
continue
}
used[i] = true
pair := roundActionPair{}
if f.isOurPlayerID(act.GetPlayerID()) {
pair.our = act
} else {
pair.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
}
// 根据动作类型执行一回合结算
func (f *FightC) resolveRound(p1Action, p2Action action.BattleActionI) {
if p1Action == nil || p2Action == nil {
cool.Logger.Debug(context.Background(), "某方未选择动作,自动跳过结算")
func (f *FightC) resolveRound(actions []action.BattleActionI) {
if len(actions) == 0 {
cool.Logger.Debug(context.Background(), "当前回合没有可执行动作,自动跳过结算")
return
}
for _, pair := range f.buildRoundActionPairs(actions) {
if f.closefight {
return
}
f.resolveActionPair(pair.our, pair.opp)
}
}
func (f *FightC) resolveActionPair(p1Action, p2Action action.BattleActionI) {
if p1Action == nil && p2Action == nil {
return
}
@@ -324,11 +461,15 @@ func (f *FightC) handleActiveSwitchAction(_ *action.ActiveSwitchAction, otherAct
// handleUseItemAction 处理使用道具动作
func (f *FightC) handleUseItemAction(itemAction *action.UseItemAction, otherAction action.BattleActionI) {
f.setActionAttackValue(itemAction)
f.handleItemAction(itemAction)
input := f.GetInputByAction(itemAction, false)
if input.CurPet[0].Info.Hp <= 0 {
input.CurPet[0].Info.Hp = 1
if input == nil {
return
}
if currentPet := input.PrimaryCurPet(); currentPet != nil && currentPet.Info.Hp <= 0 {
currentPet.Info.Hp = 1
}
if skillAction, ok := otherAction.(*action.SelectSkillAction); ok {
@@ -346,28 +487,32 @@ func (f *FightC) handleUseItemAction(itemAction *action.UseItemAction, otherActi
// 使用道具的逻辑封装
func (f *FightC) handleItemAction(a *action.UseItemAction) {
source := f.GetInputByAction(a, false)
target := f.GetInputByAction(a, true)
if source == nil {
return
}
item, ok := xmlres.ItemsMAP[int(a.ItemID)]
if !ok {
return
}
r := f.GetInputByAction(a, false).Player.(*player.Player).Service.Item.CheakItem(uint32(a.ItemID))
r := source.Player.(*player.Player).Service.Item.CheakItem(uint32(a.ItemID))
if r <= 0 {
return
}
f.GetInputByAction(a, false).Player.(*player.Player).Service.Item.UPDATE(a.ItemID, -1)
source.Player.(*player.Player).Service.Item.UPDATE(a.ItemID, -1)
switch {
case gconv.Int(item.Bonus) != 0:
if f.Opp[0].CanCapture > 0 { //可以捕捉
f.Opp[0].CurPet[0].CatchRate = f.Opp[0].CanCapture
ok, _ := f.Our[0].Capture(f.Opp[0].CurPet[0], a.ItemID, -1)
our := f.Our[0].Player.(*player.Player)
if target != nil && target.CanCapture > 0 && target.PrimaryCurPet() != nil { //可以捕捉
target.PrimaryCurPet().CatchRate = target.CanCapture
ok, _ := source.Capture(target.PrimaryCurPet(), a.ItemID, -1)
our := source.Player.(*player.Player)
if ok {
r := input.GetFunc(int64(item.ID))
if r != nil {
r.Exec(f, &f.Opp[0].Player.GetInfo().PetList[0])
r.Exec(f, &target.Player.GetInfo().PetList[0])
}
f.Reason = model.BattleOverReason.Cacthok
f.closefight = true
@@ -378,24 +523,32 @@ func (f *FightC) handleItemAction(a *action.UseItemAction) {
}
case gconv.Int(item.HP) != 0:
addhp := item.HP
f.GetInputByAction(a, false).Heal(f.GetInputByAction(a, false), a, alpacadecimal.NewFromInt(int64(addhp)))
source.Heal(source, a, alpacadecimal.NewFromInt(int64(addhp)))
f.Broadcast(func(ff *input.Input) {
currentPet := source.PrimaryCurPet()
if currentPet == nil {
return
}
ff.Player.SendPackCmd(2406, &info.UsePetIteminfo{
UserID: f.GetInputByAction(a, false).UserID,
UserID: source.UserID,
ChangeHp: int32(addhp),
ItemID: uint32(item.ID),
UserHp: uint32(f.GetInputByAction(a, false).CurPet[0].Info.Hp),
UserHp: uint32(currentPet.Info.Hp),
})
})
case gconv.Int(item.PP) != 0:
f.GetInputByAction(a, false).HealPP(item.PP)
source.HealPP(item.PP)
f.Broadcast(func(ff *input.Input) {
currentPet := source.PrimaryCurPet()
if currentPet == nil {
return
}
ff.Player.SendPackCmd(2406, &info.UsePetIteminfo{
UserID: f.GetInputByAction(a, false).UserID,
UserID: source.UserID,
ItemID: uint32(item.ID),
UserHp: uint32(f.GetInputByAction(a, false).CurPet[0].Info.Hp),
UserHp: uint32(currentPet.Info.Hp),
})
})
@@ -445,8 +598,15 @@ func (f *FightC) handleSkillActions(a1, a2 action.BattleActionI) {
// 根据玩家ID返回对应对象
func (f *FightC) getPlayerByID(id uint32) common.PlayerI {
if id == f.Our[0].Player.GetInfo().UserID {
return f.Our[0].Player
for _, player := range f.OurPlayers {
if player != nil && player.GetInfo().UserID == id {
return player
}
}
return f.Opp[0].Player
for _, player := range f.OppPlayers {
if player != nil && player.GetInfo().UserID == id {
return player
}
}
return nil
}

View File

@@ -21,7 +21,7 @@ func NewFight(p1, p2 common.PlayerI, b1, b2 []model.PetInfo, fn func(model.Fight
f.OurPlayers = []common.PlayerI{p1}
f.OppPlayers = []common.PlayerI{p2}
f.Switch = make(map[uint32]*action.ActiveSwitchAction)
f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction)
f.callback = fn //战斗结束的回调
f.quit = make(chan struct{})
f.over = make(chan struct{})
@@ -58,7 +58,6 @@ func NewFight(p1, p2 common.PlayerI, b1, b2 []model.PetInfo, fn func(model.Fight
ff.SetOPP(f.GetInputByPlayer(ff.Player, true))
})
f.FightStartOutboundInfo = f.buildFightStartInfo()
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
f.Opp[0].Finished = true //PVE 默认boss数据直接加载完成
@@ -67,11 +66,10 @@ func NewFight(p1, p2 common.PlayerI, b1, b2 []model.PetInfo, fn func(model.Fight
if f.Opp[0].Player.(*player.AI_player).CanCapture > 0 {
f.Opp[0].CanCapture = f.Opp[0].Player.(*player.AI_player).CanCapture
f.FightStartOutboundInfo.Info2.Catchable = 1 //可以捕捉就置1
}
f.Opp[0].AttackValue.Prop = f.Opp[0].Player.(*player.AI_player).Prop
f.FightStartOutboundInfo.Info2.Prop = f.Opp[0].AttackValue.Prop
}
f.FightStartOutboundInfo = f.buildFightStartInfo()
f.Broadcast(func(ff *input.Input) {

View File

@@ -155,13 +155,15 @@ var BattleOverReason = enum.New[struct {
// AttackValue 战斗中的攻击数值信息
type AttackValue struct {
UserID uint32 `json:"userId" fieldDescription:"玩家的米米号 与野怪对战userid = 0"`
SkillID uint32 `json:"skillId" fieldDescription:"使用技能的id"`
AttackTime uint32 `json:"attackTime" fieldDescription:"是否击中 如果为0 则miss 如果为1 则击中,2为必中"`
LostHp uint32 `json:"lostHp" fieldDescription:"我方造成的伤害"`
GainHp int32 `json:"gainHp" fieldDescription:"我方获得血量"`
RemainHp int32 `json:"remainHp" fieldDescription:"我方剩余血量"`
MaxHp uint32 `json:"maxHp" fieldDescription:"我方最大血量"`
UserID uint32 `json:"userId" fieldDescription:"玩家的米米号 与野怪对战userid = 0"`
ActorIndex uint32 `json:"actorIndex" fieldDescription:"当前动作所属战斗位"`
TargetIndex uint32 `json:"targetIndex" fieldDescription:"当前动作目标战斗位"`
SkillID uint32 `json:"skillId" fieldDescription:"使用技能的id"`
AttackTime uint32 `json:"attackTime" fieldDescription:"是否击中 如果为0 则miss 如果为1 则击中,2为必中"`
LostHp uint32 `json:"lostHp" fieldDescription:"我方造成的伤害"`
GainHp int32 `json:"gainHp" fieldDescription:"我方获得血量"`
RemainHp int32 `json:"remainHp" fieldDescription:"我方剩余血量"`
MaxHp uint32 `json:"maxHp" fieldDescription:"我方最大血量"`
//颜色
State uint32 `json:"state" `
SkillListLen uint32 `struc:"sizeof=SkillList"`