diff --git a/docs/effect-unimplemented-tasks/task-325-effects-2240-2244.md b/docs/effect-unimplemented-tasks/task-325-effects-2240-2244.md deleted file mode 100644 index 3a563c9eb..000000000 --- a/docs/effect-unimplemented-tasks/task-325-effects-2240-2244.md +++ /dev/null @@ -1,35 +0,0 @@ -# Task 325: Effects 2240-2244 - -## 目标 - -- 补齐以下 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 2240 -- `argsNum`: `1` -- `info`: `解除自身所处的异常状态,解除成功则本次攻击威力提升{0}%` - -### Effect 2241 -- `argsNum`: `2` -- `info`: `消除对手能力提升状态,消除成功则{0}回合内对手体力恢复效果减少{1}%` - -### Effect 2242 -- `argsNum`: `0` -- `info`: `此技能PP值不为满时先制+1` - -### Effect 2243 -- `argsNum`: `0` -- `info`: `若先出手则自身下次受到致死伤害时重生` - -### Effect 2244 -- `argsNum`: `1` -- `info`: `{0}回合内免疫受到的非限制类异常状态` - -## 备注 - -- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。 -- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。 diff --git a/docs/effect-unimplemented-tasks/task-331-effects-2270-2274.md b/docs/effect-unimplemented-tasks/task-331-effects-2270-2274.md deleted file mode 100644 index 037fa76ac..000000000 --- a/docs/effect-unimplemented-tasks/task-331-effects-2270-2274.md +++ /dev/null @@ -1,38 +0,0 @@ -# Task 331: Effects 2270-2274 - -## 目标 - -- 补齐以下 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 2270 -- `argsNum`: `3` -- `info`: `免疫下{0}次受到的攻击,触发成功则{1}%令对手{2}` -- `param`: `1,2,2` - -### Effect 2271 -- `argsNum`: `4` -- `info`: `{0}回合内若自身能力提升状态被消除或吸取则{1}%令对手{2},未触发则自身下{3}次攻击必定致命一击` -- `param`: `1,2,2` - -### Effect 2272 -- `argsNum`: `1` -- `info`: `己方有其他精灵存活时造成的攻击伤害提升{0}%` - -### Effect 2273 -- `argsNum`: `2` -- `info`: `对手选择攻击技能时此技能先制+3且{0}%令对手{1}` -- `param`: `1,1,1` - -### Effect 2274 -- `argsNum`: `1` -- `info`: `恢复自身全部PP值,每恢复1点吸取对手{0}点体力` - -## 备注 - -- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。 -- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。 diff --git a/docs/effect-unimplemented-tasks/task-332-effects-2275-2279.md b/docs/effect-unimplemented-tasks/task-332-effects-2275-2279.md deleted file mode 100644 index 7a900a87c..000000000 --- a/docs/effect-unimplemented-tasks/task-332-effects-2275-2279.md +++ /dev/null @@ -1,35 +0,0 @@ -# Task 332: Effects 2275-2279 - -## 目标 - -- 补齐以下 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 2275 -- `argsNum`: `0` -- `info`: `未击败对手则将对手标记为狩杀之的` - -### Effect 2276 -- `argsNum`: `0` -- `info`: `将对方场下对自身克制系数最高且存活的一只非狩杀之的精灵标记为狩杀之的` - -### Effect 2277 -- `argsNum`: `0` -- `info`: `将对方场下对自身克制系数最低且存活的一只非狩杀之的精灵标记为狩杀之的` - -### Effect 2278 -- `argsNum`: `3` -- `info`: `若对手为狩杀之的则造成的攻击伤害提升{0}%,若对手不为狩杀之的则自身当回合无法造成攻击伤害且附加等同于狩杀之的体力{1}%的百分比伤害,自身拥有狩杀之魂时上述效果提升{2}%` - -### Effect 2279 -- `argsNum`: `1` -- `info`: `自身击败狩杀之的时,对方下只精灵出战时将被标记为狩杀之的,且自身每有1层狩杀之魂,自身下次攻击造成的伤害提升{0}%` - -## 备注 - -- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。 -- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。 diff --git a/docs/effect-unimplemented-tasks/task-333-effects-2280-2284.md b/docs/effect-unimplemented-tasks/task-333-effects-2280-2284.md deleted file mode 100644 index 005867868..000000000 --- a/docs/effect-unimplemented-tasks/task-333-effects-2280-2284.md +++ /dev/null @@ -1,35 +0,0 @@ -# Task 333: Effects 2280-2284 - -## 目标 - -- 补齐以下 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 2280 -- `argsNum`: `0` -- `info`: `技能无效时,对手下回合无法主动切换精灵` - -### Effect 2281 -- `argsNum`: `0` -- `info`: `若先出手则自身下次受到致死伤害时保留1点体力` - -### Effect 2282 -- `argsNum`: `3` -- `info`: `{0}回合内使用技能恢复自身最大体力1/{1}并造成等量百分比伤害,若对手免疫百分比伤害则附加自身最大体力{2}%的真实伤害` - -### Effect 2283 -- `argsNum`: `3` -- `info`: `消耗自身等同于护盾、护罩之和的体力平均恢复至不在场精灵,若消耗的点数大于{0}点则额外消除双方的护盾、护罩并获得消除量{1}%的临时体力上限直到下场,最高叠加至体力上限的{2}%` - -### Effect 2284 -- `argsNum`: `2` -- `info`: `若对手不为普通系则剥夺对手的系别,每剥夺1种附加1种控制类异常状态,每有1种异常状态未触发自身下{0}次行动结束后进行额外行动以触发咒骸魂契:附加{1}点所剥夺系别的伤害` - -## 备注 - -- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。 -- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。 diff --git a/docs/effect-unimplemented-tasks/task-334-effects-2285-2289.md b/docs/effect-unimplemented-tasks/task-334-effects-2285-2289.md deleted file mode 100644 index eb93071dc..000000000 --- a/docs/effect-unimplemented-tasks/task-334-effects-2285-2289.md +++ /dev/null @@ -1,35 +0,0 @@ -# Task 334: Effects 2285-2289 - -## 目标 - -- 补齐以下 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 2285 -- `argsNum`: `3` -- `info`: `{0}回合内对手无法通过技能恢复体力,若对手当回合未选择技能则改为{1}回合内对手体力恢复效果减少{2}%` - -### Effect 2286 -- `argsNum`: `2` -- `info`: `对手下{0}次回合类效果、能力提升效果被消除时,体力上限减少{1}点` - -### Effect 2287 -- `argsNum`: `2` -- `info`: `{0}回合内使用技能恢复自身最大体力的1/{1}并造成等量百分比伤害,恢复效果溢出时转化为等量的护盾` - -### Effect 2288 -- `argsNum`: `0` -- `info`: `技能无效时,为对手附加3回合的琼花仙荼` - -### Effect 2289 -- `argsNum`: `2` -- `info`: `自身每点亮1颗雷辰威力提升{0}点,若自身未点亮雷辰则己方每存活1只精灵造成的攻击伤害不低于{1}` - -## 备注 - -- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。 -- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。 diff --git a/docs/effect-unimplemented-tasks/task-335-effects-2290-2294.md b/docs/effect-unimplemented-tasks/task-335-effects-2290-2294.md deleted file mode 100644 index 7936d48ae..000000000 --- a/docs/effect-unimplemented-tasks/task-335-effects-2290-2294.md +++ /dev/null @@ -1,35 +0,0 @@ -# Task 335: Effects 2290-2294 - -## 目标 - -- 补齐以下 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 2290 -- `argsNum`: `0` -- `info`: `自身每点亮1颗雷辰全属性+1` - -### Effect 2291 -- `argsNum`: `3` -- `info`: `自身武道衍化轨迹为正转时{0}回合内对手无法通过技能恢复体力,为逆转时{1}回合内对手造成的攻击伤害减少{2}%` - -### Effect 2292 -- `argsNum`: `1` -- `info`: `将自身的武道衍化轨迹改为逆转并将武道正转期间抵挡的伤害以固定伤害的形式反馈给对手,最高不超过自身最大体力的{0}%` - -### Effect 2293 -- `argsNum`: `1` -- `info`: `复原自身的武道衍化轨迹,下{0}次衍化时时全属性+1` - -### Effect 2294 -- `argsNum`: `0` -- `info`: `衍化自身的武道` - -## 备注 - -- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。 -- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。 diff --git a/logic/service/fight/boss/NewSeIdx_113.go b/logic/service/fight/boss/NewSeIdx_113.go index c33b7085d..a4f92977a 100644 --- a/logic/service/fight/boss/NewSeIdx_113.go +++ b/logic/service/fight/boss/NewSeIdx_113.go @@ -33,5 +33,5 @@ func (e *NewSel113) DamageLockEx(t *info.DamageZone) bool { return true } func init() { - input.InitEffect(input.EffectType.NewSel, 113, &NewSel113{}) + input.InitEffect(input.EffectType.NewSel, 213, &NewSel113{}) } diff --git a/logic/service/fight/boss/NewSeIdx_144.go b/logic/service/fight/boss/NewSeIdx_144.go index 6897348f6..a4be545df 100644 --- a/logic/service/fight/boss/NewSeIdx_144.go +++ b/logic/service/fight/boss/NewSeIdx_144.go @@ -33,5 +33,5 @@ func (e *NewSel144) Action_end_ex() bool { return true } func init() { - input.InitEffect(input.EffectType.NewSel, 144, &NewSel144{}) + input.InitEffect(input.EffectType.NewSel, 244, &NewSel144{}) } diff --git a/logic/service/fight/boss/NewSeIdx_224.go b/logic/service/fight/boss/NewSeIdx_224.go index 658ef10fc..86158827f 100644 --- a/logic/service/fight/boss/NewSeIdx_224.go +++ b/logic/service/fight/boss/NewSeIdx_224.go @@ -40,5 +40,5 @@ func (e *NewSel224) Skill_Use() bool { return true } func init() { - input.InitEffect(input.EffectType.NewSel, 224, &NewSel224{}) + input.InitEffect(input.EffectType.NewSel, 324, &NewSel224{}) } diff --git a/logic/service/fight/boss/NewSeIdx_41.go b/logic/service/fight/boss/NewSeIdx_41.go index f7183a046..b4fd6f497 100644 --- a/logic/service/fight/boss/NewSeIdx_41.go +++ b/logic/service/fight/boss/NewSeIdx_41.go @@ -22,5 +22,5 @@ func (e *NewSel41) Skill_Use_ex() bool { return true } func init() { - input.InitEffect(input.EffectType.NewSel, 41, &NewSel41{}) + input.InitEffect(input.EffectType.NewSel, 141, &NewSel41{}) } diff --git a/logic/service/fight/boss/json_eid_advanced.go b/logic/service/fight/boss/json_eid_advanced.go new file mode 100644 index 000000000..ba04786a1 --- /dev/null +++ b/logic/service/fight/boss/json_eid_advanced.go @@ -0,0 +1,327 @@ +package effect + +import ( + "blazing/logic/service/fight/action" + "blazing/logic/service/fight/info" + "blazing/logic/service/fight/input" + + "github.com/alpacahq/alpacadecimal" + "github.com/gogf/gf/v2/util/grand" +) + +func bossJsonTransferPositive(from, to *input.Input) bool { + changed := false + for i := 0; i < 6; i++ { + if from.AttackValue.Prop[i] <= 0 { + continue + } + changed = true + stage := from.AttackValue.Prop[i] + from.AttackValue.Prop[i] = 0 + to.AttackValue.Prop[i] += stage + if to.AttackValue.Prop[i] > 6 { + to.AttackValue.Prop[i] = 6 + } + } + return changed +} + +func bossJsonActionMatches(skill *info.SkillEntity, act *action.SelectSkillAction, userID uint32) bool { + if skill == nil || act == nil || act.SkillEntity == nil || act.PlayerID != userID { + return false + } + return act.SkillEntity.XML.ID == skill.XML.ID +} + +// 41: 每回合恢复固定体力 +type BossJsonEid41 struct{ BossJsonEid0 } + +func (e *BossJsonEid41) TurnStart(fattack, sattack *action.SelectSkillAction) { + if !e.ownerActive() { + return + } + heal := bossJsonFirstPositiveArg(e.Args(), 0) + if heal > 0 { + e.Ctx().Our.Heal(e.Ctx().Our, nil, alpacadecimal.NewFromInt(int64(heal))) + } +} + +// 113: 对手使用技能后全属性-1 +type BossJsonEid113 struct{ BossJsonEid0 } + +func (e *BossJsonEid113) Action_end_ex() bool { + if !e.ownerActive() || e.Ctx().SkillEntity == nil { + return true + } + stage := int8(bossJsonFirstPositiveArg(e.Args(), 1)) + bossJsonBoostAll(e.Ctx().Opp, e.Ctx().Our, -stage) + return true +} + +// 144: 对手技能结束时,消除对手的能力提升 +type BossJsonEid144 struct{ BossJsonEid0 } + +func (e *BossJsonEid144) Action_end_ex() bool { + if !e.ownerActive() || e.Ctx().SkillEntity == nil { + return true + } + bossJsonClearPositive(e.Ctx().Opp) + return true +} + +// 224: 首次死亡后恢复全部体力,并使全能力等级等于指定值 +type BossJsonEid224 struct { + BossJsonEid0 + used bool +} + +func (e *BossJsonEid224) revive() bool { + if !e.ownerActive() || e.used || e.Ctx().Our.CurrentPet.Info.Hp != 0 { + return true + } + e.used = true + e.Ctx().Our.CurrentPet.Info.Hp = e.Ctx().Our.CurrentPet.Info.MaxHp + e.Ctx().Our.HealPP(-1) + stageSeed := bossJsonIntArg(e.Args(), len(e.Args())-1, 6) + stage := int8(stageSeed - 6) + if stage < 0 { + stage = 0 + } + if stage > 6 { + stage = 6 + } + bossJsonSetAll(e.Ctx().Our, stage) + return true +} + +func (e *BossJsonEid224) Action_end() bool { return e.revive() } +func (e *BossJsonEid224) Action_end_ex() bool { return e.revive() } + +// 2095: 对手存在能力提升或回合类效果时伤害提升 +type BossJsonEid2095 struct{ BossJsonEid0 } + +func (e *BossJsonEid2095) Damage_Mul(zone *info.DamageZone) bool { + if !e.ownerActive() || zone.Type != info.DamageType.Red { + return true + } + if !bossJsonHasPositive(e.Ctx().Opp) && !bossJsonAnyTurnEffect(e.Ctx().Opp) { + return true + } + bonus := bossJsonFirstPositiveArg(e.Args(), 0) + zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(int64(100 + bonus))).Div(alpacadecimal.NewFromInt(100)) + return true +} + +// 2104: 回合开始提升体力上限;回合结束恢复已损失体力的一定比例 +type BossJsonEid2104 struct{ BossJsonEid0 } + +func (e *BossJsonEid2104) TurnStart(fattack, sattack *action.SelectSkillAction) { + if !e.ownerActive() { + return + } + growPercent := bossJsonIntArg(e.Args(), 1, 0) + if growPercent <= 0 { + return + } + add := int(e.Ctx().Our.CurrentPet.Info.MaxHp) * growPercent / 100 + if add <= 0 { + return + } + e.Ctx().Our.CurrentPet.Info.MaxHp += uint32(add) + e.Ctx().Our.CurrentPet.Info.Hp += uint32(add) +} + +func (e *BossJsonEid2104) TurnEnd() { + if !e.ownerActive() { + return + } + recoverPercent := bossJsonIntArg(e.Args(), 0, 0) + if recoverPercent <= 0 || e.Ctx().Our.CurrentPet.Info.MaxHp <= e.Ctx().Our.CurrentPet.Info.Hp { + return + } + missing := int(e.Ctx().Our.CurrentPet.Info.MaxHp - e.Ctx().Our.CurrentPet.Info.Hp) + heal := missing * recoverPercent / 100 + if heal > 0 { + e.Ctx().Our.Heal(e.Ctx().Our, nil, alpacadecimal.NewFromInt(int64(heal))) + } +} + +// 2103: 吸取对手能力提升,否则随机削弱并附加给自身;回合结束吸取固定体力 +type BossJsonEid2103 struct{ BossJsonEid0 } + +func (e *BossJsonEid2103) TurnStart(fattack, sattack *action.SelectSkillAction) { + if !e.ownerActive() { + return + } + if bossJsonTransferPositive(e.Ctx().Opp, e.Ctx().Our) { + return + } + count := bossJsonIntArg(e.Args(), 0, 0) + stage := int8(bossJsonIntArg(e.Args(), 1, 0)) + if count <= 0 || stage <= 0 { + return + } + used := map[int]struct{}{} + for len(used) < count && len(used) < 6 { + used[grand.Intn(6)] = struct{}{} + } + for idx := range used { + e.Ctx().Opp.SetProp(e.Ctx().Our, int8(idx), -stage) + e.Ctx().Our.SetProp(e.Ctx().Our, int8(idx), stage) + } +} + +func (e *BossJsonEid2103) TurnEnd() { + if !e.ownerActive() { + return + } + value := bossJsonIntArg(e.Args(), 2, 0) + if value <= 0 { + return + } + damage := alpacadecimal.NewFromInt(int64(value)) + e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: damage}) + e.Ctx().Our.Heal(e.Ctx().Our, nil, damage) +} + +// 2105: 低血量时获得若干回合力量(这里按伤害翻倍处理) +type BossJsonEid2105 struct { + BossJsonEid0 + rounds int +} + +func (e *BossJsonEid2105) Damage_Mul(zone *info.DamageZone) bool { + if !e.ownerActive() || e.rounds <= 0 || zone.Type != info.DamageType.Red { + return true + } + zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(2)) + return true +} + +func (e *BossJsonEid2105) TurnEnd() { + if !e.ownerActive() { + return + } + if e.rounds > 0 { + e.rounds-- + } + threshold := bossJsonIntArg(e.Args(), 0, 0) + grant := bossJsonIntArg(e.Args(), 1, 0) + if threshold <= 0 || grant <= 0 || e.Ctx().Our.CurrentPet.Info.MaxHp == 0 { + return + } + if int(e.Ctx().Our.CurrentPet.Info.Hp)*100 < int(e.Ctx().Our.CurrentPet.Info.MaxHp)*threshold && e.rounds < grant { + e.rounds = grant + } +} + +// 2137: 恢复效果逐回合衰减,衰减前自身增伤并附加固伤 +type BossJsonEid2137 struct { + BossJsonEid0 + healRate int +} + +func (e *BossJsonEid2137) TurnStart(fattack, sattack *action.SelectSkillAction) { + if !e.ownerActive() { + return + } + if e.healRate == 0 { + e.healRate = 100 + } + dec := bossJsonIntArg(e.Args(), 0, 0) + if dec > 0 { + e.healRate -= dec + if e.healRate < 0 { + e.healRate = 0 + } + } +} + +func (e *BossJsonEid2137) Heal_Pre(_ action.BattleActionI, amount *int) bool { + if !e.ownerActive() || e.healRate >= 100 { + return true + } + *amount = *amount * e.healRate / 100 + return true +} + +func (e *BossJsonEid2137) DamageAdd(zone *info.DamageZone) bool { + if !e.ownerActive() || e.healRate <= 0 || zone.Type != info.DamageType.Red { + return true + } + fixed := bossJsonIntArg(e.Args(), 2, 0) + if fixed > 0 { + zone.Damage = zone.Damage.Add(alpacadecimal.NewFromInt(int64(fixed))) + } + return true +} + +func (e *BossJsonEid2137) Damage_Mul(zone *info.DamageZone) bool { + if !e.ownerActive() || e.healRate <= 0 || zone.Type != info.DamageType.Red { + return true + } + bonus := bossJsonIntArg(e.Args(), 1, 0) + zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(int64(100 + bonus))).Div(alpacadecimal.NewFromInt(100)) + return true +} + +// 1621: 对手攻击技能后出手则无效 +type BossJsonEid1621 struct{ BossJsonEid0 } + +func (e *BossJsonEid1621) ActionStart(firstAttack, secondAttack *action.SelectSkillAction) bool { + if !e.ownerActive() || !bossJsonIsAttackSkill(e.Ctx().SkillEntity) { + return true + } + userID := e.Ctx().Our.Player.GetInfo().UserID + if !bossJsonActionMatches(e.Ctx().SkillEntity, secondAttack, userID) { + return true + } + if firstAttack == nil || !bossJsonIsAttackSkill(firstAttack.SkillEntity) { + return true + } + return false +} + +// 1254: 先出手则当回合免疫受到的攻击伤害 +type BossJsonEid1254 struct { + BossJsonEid0 + immuneThisTurn bool +} + +func (e *BossJsonEid1254) ActionStart(firstAttack, secondAttack *action.SelectSkillAction) bool { + e.immuneThisTurn = false + if !e.ownerActive() || !bossJsonIsAttackSkill(e.Ctx().SkillEntity) { + return true + } + userID := e.Ctx().Our.Player.GetInfo().UserID + e.immuneThisTurn = bossJsonActionMatches(e.Ctx().SkillEntity, firstAttack, userID) + return true +} + +func (e *BossJsonEid1254) DamageLockEx(zone *info.DamageZone) bool { + if !e.ownerActive() || !e.immuneThisTurn || zone.Type != info.DamageType.Red || e.Ctx().SkillEntity == nil { + return true + } + if bossJsonIsAttackSkill(e.Ctx().SkillEntity) { + zone.Damage = alpacadecimal.Zero + } + return true +} + +func (e *BossJsonEid1254) TurnEnd() { + e.immuneThisTurn = false +} + +func init() { + input.InitEffect(input.EffectType.NewSel, 41, &BossJsonEid41{}) + input.InitEffect(input.EffectType.NewSel, 113, &BossJsonEid113{}) + input.InitEffect(input.EffectType.NewSel, 144, &BossJsonEid144{}) + input.InitEffect(input.EffectType.NewSel, 224, &BossJsonEid224{}) + input.InitEffect(input.EffectType.NewSel, 2095, &BossJsonEid2095{}) + input.InitEffect(input.EffectType.NewSel, 2104, &BossJsonEid2104{}) + input.InitEffect(input.EffectType.NewSel, 2103, &BossJsonEid2103{}) + input.InitEffect(input.EffectType.NewSel, 2105, &BossJsonEid2105{}) + input.InitEffect(input.EffectType.NewSel, 2137, &BossJsonEid2137{}) + input.InitEffect(input.EffectType.NewSel, 1621, &BossJsonEid1621{}) + input.InitEffect(input.EffectType.NewSel, 1254, &BossJsonEid1254{}) +} diff --git a/logic/service/fight/boss/json_eid_basic.go b/logic/service/fight/boss/json_eid_basic.go new file mode 100644 index 000000000..6acc51a23 --- /dev/null +++ b/logic/service/fight/boss/json_eid_basic.go @@ -0,0 +1,379 @@ +package effect + +import ( + "blazing/logic/service/fight/action" + "blazing/logic/service/fight/info" + "blazing/logic/service/fight/input" + + "github.com/alpacahq/alpacadecimal" + "github.com/gogf/gf/v2/util/grand" +) + +type BossJsonEid0 struct { + NewSel0 +} + +func (e *BossJsonEid0) ownerActive() bool { + return e.IsOwner() +} + +func bossJsonIntArg(args []alpacadecimal.Decimal, idx int, def int) int { + if idx >= 0 && idx < len(args) { + return int(args[idx].IntPart()) + } + return def +} + +func bossJsonFirstPositiveArg(args []alpacadecimal.Decimal, def int) int { + for _, arg := range args { + if arg.IntPart() > 0 { + return int(arg.IntPart()) + } + } + return def +} + +func bossJsonIsAttackSkill(skill *info.SkillEntity) bool { + return skill != nil && skill.Category() != info.Category.STATUS +} + +func bossJsonBoostAll(target, source *input.Input, delta int8) { + for i := 0; i < 6; i++ { + target.SetProp(source, int8(i), delta) + } +} + +func bossJsonSetAll(target *input.Input, value int8) { + for i := 0; i < 6; i++ { + target.AttackValue.Prop[i] = value + } +} + +func bossJsonClearPositive(target *input.Input) bool { + changed := false + for i := 0; i < 6; i++ { + if target.AttackValue.Prop[i] > 0 { + target.AttackValue.Prop[i] = 0 + changed = true + } + } + return changed +} + +func bossJsonHasPositive(target *input.Input) bool { + for i := 0; i < 6; i++ { + if target.AttackValue.Prop[i] > 0 { + return true + } + } + return false +} + +func bossJsonAnyTurnEffect(target *input.Input) bool { + for _, effect := range target.Effects { + if effect.Alive() && effect.Duration() > 0 { + return true + } + } + return false +} + +// 1261/2346: 神话 +// 免疫异常状态和能力下降状态,所有技能必中且PP值无限 +type BossJsonEidMyth struct{ BossJsonEid0 } + +func (e *BossJsonEidMyth) PropBefer(in *input.Input, prop int8, level int8) bool { + if !e.ownerActive() || in == e.Ctx().Our { + return true + } + return level >= 0 +} + +func (e *BossJsonEidMyth) EFFect_Befer(in *input.Input, effEffect input.Effect) bool { + if !e.ownerActive() || in != e.Ctx().Opp { + return true + } + return !input.IS_Stat(effEffect) +} + +func (e *BossJsonEidMyth) ActionStart(a, b *action.SelectSkillAction) bool { + if !e.ownerActive() || e.Ctx().SkillEntity == nil { + return true + } + e.Ctx().SkillEntity.XML.MustHit = 1 + return true +} + +func (e *BossJsonEidMyth) HookPP(count *int) bool { + if !e.ownerActive() { + return true + } + *count = 0 + return true +} + +// 600: 所有技能必定先手 +type BossJsonEid600 struct{ BossJsonEid0 } + +func (e *BossJsonEid600) ComparePre(fattack, sattack *action.SelectSkillAction) bool { + if !e.ownerActive() { + return true + } + userID := e.Ctx().Our.Player.GetInfo().UserID + if fattack != nil && fattack.PlayerID == userID && fattack.SkillEntity != nil { + fattack.SkillEntity.XML.Priority += 1000 + } + if sattack != nil && sattack.PlayerID == userID && sattack.SkillEntity != nil { + sattack.SkillEntity.XML.Priority += 1000 + } + return true +} + +// 390: 自身使用技能后全属性+1 +type BossJsonEid390 struct{ BossJsonEid0 } + +func (e *BossJsonEid390) Action_end() bool { + if !e.ownerActive() || e.Ctx().SkillEntity == nil { + return true + } + bossJsonBoostAll(e.Ctx().Our, e.Ctx().Our, int8(bossJsonFirstPositiveArg(e.Args(), 1))) + return true +} + +// 792: 格挡物攻/特攻 +type BossJsonEid792 struct{ BossJsonEid0 } + +func (e *BossJsonEid792) DamageLockEx(zone *info.DamageZone) bool { + if !e.ownerActive() || zone.Type != info.DamageType.Red || e.Ctx().SkillEntity == nil { + return true + } + mode := bossJsonIntArg(e.Args(), 0, 0) + if mode == 1 && e.Ctx().SkillEntity.Category() == info.Category.PHYSICAL { + zone.Damage = alpacadecimal.Zero + } + if mode == 2 && e.Ctx().SkillEntity.Category() == info.Category.SPECIAL { + zone.Damage = alpacadecimal.Zero + } + return true +} + +// 219: 抵挡未打出致命一击的攻击 +type BossJsonEid219 struct{ BossJsonEid0 } + +func (e *BossJsonEid219) DamageLockEx(zone *info.DamageZone) bool { + if !e.ownerActive() || zone.Type != info.DamageType.Red || e.Ctx().SkillEntity == nil { + return true + } + if e.Ctx().SkillEntity.Crit == 0 { + zone.Damage = alpacadecimal.Zero + } + return true +} + +// 208: 抵挡非先制攻击 +type BossJsonEid208 struct{ BossJsonEid0 } + +func (e *BossJsonEid208) DamageLockEx(zone *info.DamageZone) bool { + if !e.ownerActive() || zone.Type != info.DamageType.Red || e.Ctx().SkillEntity == nil { + return true + } + if e.Ctx().SkillEntity.XML.Priority <= 0 { + zone.Damage = alpacadecimal.Zero + } + return true +} + +// 1216: 免疫固定伤害、百分比伤害 +type BossJsonEid1216 struct{ BossJsonEid0 } + +func (e *BossJsonEid1216) DamageLockEx(zone *info.DamageZone) bool { + if !e.ownerActive() { + return true + } + if zone.Type == info.DamageType.Fixed || zone.Type == info.DamageType.Percent { + zone.Damage = alpacadecimal.Zero + } + return true +} + +// 805: 死亡后概率满血复活 +type BossJsonEid805 struct { + BossJsonEid0 + used bool +} + +func (e *BossJsonEid805) revive() bool { + if !e.ownerActive() || e.used || e.Ctx().Our.CurrentPet.Info.Hp != 0 { + return true + } + e.used = true + if !grand.Meet(bossJsonFirstPositiveArg(e.Args(), 0), 100) { + return true + } + e.Ctx().Our.CurrentPet.Info.Hp = e.Ctx().Our.CurrentPet.Info.MaxHp + e.Ctx().Our.HealPP(-1) + return true +} + +func (e *BossJsonEid805) Action_end() bool { return e.revive() } +func (e *BossJsonEid805) Action_end_ex() bool { return e.revive() } + +// 1215: 攻击技能概率造成倍数伤害 +type BossJsonEid1215 struct{ BossJsonEid0 } + +func (e *BossJsonEid1215) Damage_Mul(zone *info.DamageZone) bool { + if !e.ownerActive() || zone.Type != info.DamageType.Red || !bossJsonIsAttackSkill(e.Ctx().SkillEntity) { + return true + } + chance := bossJsonIntArg(e.Args(), 0, 0) + multiple := bossJsonIntArg(e.Args(), 1, 1) + if chance > 0 && multiple > 1 && grand.Meet(chance, 100) { + zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(int64(multiple))) + } + return true +} + +// 280: 闪避率提升n% +type BossJsonEid280 struct{ BossJsonEid0 } + +func (e *BossJsonEid280) SkillHit_ex() bool { + if !e.ownerActive() || e.Ctx().SkillEntity == nil { + return true + } + if e.Ctx().SkillEntity.AttackTime == 2 { + return true + } + hit, _, _ := e.Input.Player.Roll(bossJsonFirstPositiveArg(e.Args(), 0), 100) + if hit { + e.Ctx().SkillEntity.SetMiss() + } + return true +} + +// 377: 与对手精灵互换属性 +type BossJsonEid377 struct { + BossJsonEid0 + used bool +} + +func (e *BossJsonEid377) Fight_Start() bool { + if !e.ownerActive() || e.used || e.Ctx().Our.CurrentPet == nil || e.Ctx().Opp.CurrentPet == nil { + return true + } + e.used = true + e.Ctx().Our.CurrentPet.Type, e.Ctx().Opp.CurrentPet.Type = e.Ctx().Opp.CurrentPet.Type, e.Ctx().Our.CurrentPet.Type + return true +} + +// 121: 概率令对手属性技能失效 +type BossJsonEid121 struct{ BossJsonEid0 } + +func (e *BossJsonEid121) SkillHit_ex() bool { + if !e.ownerActive() || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() != info.Category.STATUS { + return true + } + hit, _, _ := e.Input.Player.Roll(bossJsonFirstPositiveArg(e.Args(), 0), 100) + if hit { + e.Ctx().SkillEntity.SetMiss() + } + return true +} + +// 735: 每回合吸取固定体力 +type BossJsonEid735 struct{ BossJsonEid0 } + +func (e *BossJsonEid735) TurnStart(fattack, sattack *action.SelectSkillAction) { + if !e.ownerActive() { + return + } + value := alpacadecimal.NewFromInt(int64(bossJsonFirstPositiveArg(e.Args(), 0))) + e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: value}) + e.Ctx().Our.Heal(e.Ctx().Our, nil, value) +} + +// 261: 每回合按最大体力比例恢复 +type BossJsonEid261 struct{ BossJsonEid0 } + +func (e *BossJsonEid261) TurnStart(fattack, sattack *action.SelectSkillAction) { + if !e.ownerActive() { + return + } + num := bossJsonIntArg(e.Args(), 0, 0) + den := bossJsonIntArg(e.Args(), 1, 1) + if num <= 0 || den <= 0 { + return + } + heal := e.Ctx().Our.CurrentPet.GetMaxHP().Mul(alpacadecimal.NewFromInt(int64(num))).Div(alpacadecimal.NewFromInt(int64(den))) + e.Ctx().Our.Heal(e.Ctx().Our, nil, heal) +} + +// 209: 每n回合恢复固定体力 +type BossJsonEid209 struct { + BossJsonEid0 + turns int +} + +func (e *BossJsonEid209) TurnEnd() { + if !e.ownerActive() { + return + } + e.turns++ + interval := bossJsonIntArg(e.Args(), 0, 0) + value := bossJsonIntArg(e.Args(), 1, 0) + if interval > 0 && value > 0 && e.turns%interval == 0 { + e.Ctx().Our.Heal(e.Ctx().Our, nil, alpacadecimal.NewFromInt(int64(value))) + } +} + +// 342: 免疫低于/高于阈值的伤害 +type BossJsonEid342 struct{ BossJsonEid0 } + +func (e *BossJsonEid342) DamageLockEx(zone *info.DamageZone) bool { + if !e.ownerActive() { + return true + } + mode := bossJsonIntArg(e.Args(), 0, 0) + limit := bossJsonIntArg(e.Args(), 1, 0) + dmg := int(zone.Damage.IntPart()) + if mode == 1 && dmg < limit { + zone.Damage = alpacadecimal.Zero + } + if mode == 2 && dmg > limit { + zone.Damage = alpacadecimal.Zero + } + return true +} + +// 1233: 免疫并反弹致命一击伤害 +type BossJsonEid1233 struct{ BossJsonEid0 } + +func (e *BossJsonEid1233) DamageLockEx(zone *info.DamageZone) bool { + if !e.ownerActive() || zone.Type != info.DamageType.Red || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Crit == 0 { + return true + } + reflectDamage := zone.Damage + zone.Damage = alpacadecimal.Zero + e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: reflectDamage}) + return true +} + +func init() { + input.InitEffect(input.EffectType.NewSel, 1261, &BossJsonEidMyth{}) + input.InitEffect(input.EffectType.NewSel, 2346, &BossJsonEidMyth{}) + input.InitEffect(input.EffectType.NewSel, 600, &BossJsonEid600{}) + input.InitEffect(input.EffectType.NewSel, 390, &BossJsonEid390{}) + input.InitEffect(input.EffectType.NewSel, 792, &BossJsonEid792{}) + input.InitEffect(input.EffectType.NewSel, 219, &BossJsonEid219{}) + input.InitEffect(input.EffectType.NewSel, 208, &BossJsonEid208{}) + input.InitEffect(input.EffectType.NewSel, 1216, &BossJsonEid1216{}) + input.InitEffect(input.EffectType.NewSel, 805, &BossJsonEid805{}) + input.InitEffect(input.EffectType.NewSel, 1215, &BossJsonEid1215{}) + input.InitEffect(input.EffectType.NewSel, 280, &BossJsonEid280{}) + input.InitEffect(input.EffectType.NewSel, 377, &BossJsonEid377{}) + input.InitEffect(input.EffectType.NewSel, 121, &BossJsonEid121{}) + input.InitEffect(input.EffectType.NewSel, 735, &BossJsonEid735{}) + input.InitEffect(input.EffectType.NewSel, 261, &BossJsonEid261{}) + input.InitEffect(input.EffectType.NewSel, 209, &BossJsonEid209{}) + input.InitEffect(input.EffectType.NewSel, 342, &BossJsonEid342{}) + input.InitEffect(input.EffectType.NewSel, 1233, &BossJsonEid1233{}) +} diff --git a/logic/service/fight/effect/2220_2244.go b/logic/service/fight/effect/2220_2244.go index e1061811f..9b772c3d4 100644 --- a/logic/service/fight/effect/2220_2244.go +++ b/logic/service/fight/effect/2220_2244.go @@ -493,8 +493,8 @@ func (e *Effect2244Sub) EFFect_Befer(in *input.Input, effEffect input.Effect) bo // Effect 2240: 解除自身异常状态,解除成功则本次攻击威力提高{0}% type Effect2240 struct{ node.EffectNode } -func (e *Effect2240) Skill_Use() bool { - if len(e.Args()) == 0 { +func (e *Effect2240) SkillHit() bool { + if len(e.Args()) == 0 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS { return true } @@ -513,10 +513,7 @@ func (e *Effect2240) Skill_Use() bool { return true } - sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2240, int(e.Args()[0].IntPart())) - if sub != nil { - e.Ctx().Our.AddEffect(e.Ctx().Our, sub) - } + e.Ctx().SkillEntity.XML.Power = e.Ctx().SkillEntity.XML.Power * (100 + int(e.Args()[0].IntPart())) / 100 return true } @@ -540,18 +537,7 @@ func (e *Effect2241) Skill_Use() bool { return true } - cleared := false - for _, eff := range e.Ctx().Opp.Effects { - if eff == nil || !eff.Alive() { - continue - } - if eff.ID().GetEffectType() != input.EffectType.Status { - continue - } - eff.Alive(false) - cleared = true - } - if !cleared { + if !clearPositiveProps(e.Ctx().Opp, e.Ctx().Our) { return true } diff --git a/logic/service/fight/effect/2270_2294.go b/logic/service/fight/effect/2270_2294.go index 097635e43..bc6f87c30 100644 --- a/logic/service/fight/effect/2270_2294.go +++ b/logic/service/fight/effect/2270_2294.go @@ -1,7 +1,7 @@ package effect import ( - "blazing/common/data/xmlres" + element "blazing/common/data/Element" "blazing/logic/service/fight/action" "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" @@ -37,6 +37,133 @@ func addStatus2270(owner, target *input.Input, statusID int) bool { return true } +func markCatchTime227x(eff input.Effect) uint32 { + if eff == nil || !eff.Alive() || len(eff.Args()) == 0 { + return 0 + } + v := eff.Args()[0].IntPart() + if v <= 0 { + return 0 + } + return uint32(v) +} + +func clearHuntMarks227x(target *input.Input) { + if target == nil { + return + } + for _, eff := range target.Effects { + if eff == nil || !eff.Alive() || eff.ID().GetEffectType() != input.EffectType.Sub { + continue + } + switch int(eff.ID().Suffix()) { + case 2275, 2276, 2277: + eff.Alive(false) + } + } +} + +func applyHuntMark227x(owner, target *input.Input, markID int, catchTime uint32) bool { + if owner == nil || target == nil || catchTime == 0 { + return false + } + clearHuntMarks227x(target) + eff := owner.InitEffect(input.EffectType.Sub, markID, int(catchTime)) + if eff == nil { + return false + } + target.AddEffect(owner, eff) + return true +} + +func isCatchTimeMarked227x(target *input.Input, catchTime uint32) bool { + if target == nil || catchTime == 0 { + return false + } + for _, id := range []int{2275, 2276, 2277} { + if markCatchTime227x(target.GetEffect(input.EffectType.Sub, id)) == catchTime { + return true + } + } + return false +} + +func currentIsMarked227x(target *input.Input) bool { + if target == nil || target.CurrentPet == nil { + return false + } + return isCatchTimeMarked227x(target, target.CurrentPet.Info.CatchTime) +} + +func markedPet227x(target *input.Input) *info.BattlePetEntity { + if target == nil { + return nil + } + for _, id := range []int{2275, 2276, 2277} { + markedCatch := markCatchTime227x(target.GetEffect(input.EffectType.Sub, id)) + if markedCatch == 0 { + continue + } + if target.CurrentPet != nil && target.CurrentPet.Info.CatchTime == markedCatch && target.CurrentPet.Alive() { + return target.CurrentPet + } + for _, pet := range target.AllPet { + if pet != nil && pet.Info.CatchTime == markedCatch && pet.Alive() { + return pet + } + } + } + return nil +} + +func pickBenchByCounter227x(our, opp *input.Input, pickHighest bool) *info.BattlePetEntity { + if our == nil || opp == nil || our.CurrentPet == nil { + return nil + } + var chosen *info.BattlePetEntity + var chosenMul float64 + for _, pet := range opp.AllPet { + if pet == nil || !pet.Alive() || pet == opp.CurrentPet { + continue + } + if isCatchTimeMarked227x(opp, pet.Info.CatchTime) { + continue + } + + mul, err := element.Calculator.GetOffensiveMultiplier(pet.GetType().ID, our.CurrentPet.GetType().ID) + if err != nil { + continue + } + + if chosen == nil { + chosen = pet + chosenMul = mul + continue + } + if pickHighest && mul > chosenMul { + chosen = pet + chosenMul = mul + } + if !pickHighest && mul < chosenMul { + chosen = pet + chosenMul = mul + } + } + return chosen +} + +func huntSoulStack227x(owner *input.Input) int { + if owner == nil { + return 0 + } + eff := owner.GetEffect(input.EffectType.Sub, 2279) + sub, ok := eff.(*Effect2279Sub) + if !ok || sub == nil || !sub.Alive() { + return 0 + } + return sub.Stack() +} + // Effect 2270: 免疫n次受到的攻击,触发成功则m%令对手{2} type Effect2270 struct{ node.EffectNode } @@ -95,43 +222,63 @@ func (e *Effect2271) Skill_Use() bool { type Effect2271Sub struct { node.EffectNode + remainingCrit int + triggered bool } -func (e *Effect2271Sub) EFFect_Befer(in *input.Input, effEffect input.Effect) bool { - if in != e.Ctx().Our || effEffect == nil { +func (e *Effect2271Sub) SetArgs(t *input.Input, a ...int) { + e.EffectNode.SetArgs(t, a...) + if len(a) == 0 { + return + } + if a[0] > 0 { + e.Duration(a[0]) + return + } + e.Duration(-1) + if len(a) > 3 && a[3] > 0 { + e.remainingCrit = a[3] + } +} + +func (e *Effect2271Sub) PropBefer(source *input.Input, prop int8, level int8) bool { + if len(e.Args()) < 4 || e.Args()[0].Cmp(alpacadecimal.Zero) <= 0 { return true } - if !e.Ctx().Our.HasPropADD() && !e.Ctx().Our.HasPropSub() { + if source != e.Ctx().Opp { return true } - if len(e.Args()) >= 4 && grand.N(1, 101) <= int(e.Args()[1].IntPart()) { - addStatus2270(e.Ctx().Our, e.Ctx().Opp, int(e.Args()[2].IntPart())) - } else { - e.Ctx().Our.AddEffect(e.Ctx().Our, e.Ctx().Our.InitEffect(input.EffectType.Sub, 2271, int(e.Args()[3].IntPart()))) + if prop < 0 || int(prop) >= len(e.Ctx().Our.Prop) { + return true } + if level != 0 || e.Ctx().Our.Prop[prop] <= 0 { + return true + } + e.triggered = true + if ok, _, _ := e.Input.Player.Roll(int(e.Args()[1].IntPart()), 100); !ok { + return true + } + addStatus2270(e.Ctx().Our, e.Ctx().Opp, int(e.Args()[2].IntPart())) return true } -type Effect2271CritSub struct { - node.EffectNode - remaining int -} - -func (e *Effect2271CritSub) SetArgs(t *input.Input, a ...int) { - e.EffectNode.SetArgs(t, a...) - e.Duration(-1) - if len(a) > 0 { - e.remaining = a[0] +func (e *Effect2271Sub) TurnEnd() { + if e.Duration() == 1 && len(e.Args()) >= 4 && e.Args()[0].Cmp(alpacadecimal.Zero) > 0 && !e.triggered && e.Args()[3].Cmp(alpacadecimal.Zero) > 0 { + crit := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2271, -1, 0, 0, int(e.Args()[3].IntPart())) + if crit != nil { + e.Ctx().Our.AddEffect(e.Ctx().Our, crit) + } } + e.EffectNode.TurnEnd() } -func (e *Effect2271CritSub) SkillHit_ex() bool { - if e.remaining <= 0 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS { +func (e *Effect2271Sub) SkillHit() bool { + if e.remainingCrit <= 0 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS { return true } e.Ctx().SkillEntity.XML.CritRate = 16 - e.remaining-- - if e.remaining <= 0 { + e.remainingCrit-- + if e.remainingCrit <= 0 { e.Alive(false) } return true @@ -160,29 +307,36 @@ func (e *Effect2273) ComparePre(fattack, sattack *action.SelectSkillAction) bool if len(e.Args()) < 2 { return true } - current := actionByPlayer(fattack, sattack, e.Ctx().Opp.UserID) - if current == nil || current.SkillEntity == nil || current.SkillEntity.Category() == info.Category.STATUS { + oppAction := actionByPlayer(fattack, sattack, e.Ctx().Opp.UserID) + if oppAction == nil || oppAction.SkillEntity == nil || oppAction.SkillEntity.Category() == info.Category.STATUS { return true } - current.SkillEntity.XML.Priority += 3 - if grand.N(1, 101) <= int(e.Args()[0].IntPart()) { + ourAction := actionByPlayer(fattack, sattack, e.Ctx().Our.UserID) + if ourAction == nil || ourAction.SkillEntity == nil { + return true + } + ourAction.SkillEntity.XML.Priority += 3 + if ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100); ok { addStatus2270(e.Ctx().Our, e.Ctx().Opp, int(e.Args()[1].IntPart())) } return true } -// Effect 2274: 恢复自身全部PP值,每恢复n点吸取对手m点体力 +// Effect 2274: 恢复自身全部PP值,每恢复1点吸取对手{0}点体力 type Effect2274 struct{ node.EffectNode } func (e *Effect2274) Skill_Use() bool { - if len(e.Args()) == 0 { + if len(e.Args()) == 0 || e.Ctx().Our == nil || e.Ctx().Our.CurrentPet == nil || e.Ctx().Opp == nil || e.Ctx().Opp.CurrentPet == nil { return true } - for i := range e.Ctx().Our.CurrentPet.Info.SkillList { - if skill, ok := xmlres.SkillMap[int(e.Ctx().Our.CurrentPet.Info.SkillList[i].ID)]; ok { - e.Ctx().Our.CurrentPet.Info.SkillList[i].PP = uint32(skill.MaxPP) - } + restored := totalLostPP(e.Ctx().Our) + e.Ctx().Our.HealPP(-1) + if restored <= 0 || e.Args()[0].Cmp(alpacadecimal.Zero) <= 0 { + return true } + drain := alpacadecimal.NewFromInt(restored).Mul(e.Args()[0]) + e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: drain}) + e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, drain) return true } @@ -190,41 +344,61 @@ func (e *Effect2274) Skill_Use() bool { type Effect2275 struct{ node.EffectNode } func (e *Effect2275) Skill_Use() bool { - eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2275) - if eff != nil { - e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) + if e.Ctx().Opp == nil || e.Ctx().Opp.CurrentPet == nil || !e.Ctx().Opp.CurrentPet.Alive() { + return true } + applyHuntMark227x(e.Ctx().Our, e.Ctx().Opp, 2275, e.Ctx().Opp.CurrentPet.Info.CatchTime) return true } type Effect2275Sub struct{ node.EffectNode } +func (e *Effect2275Sub) SetArgs(t *input.Input, a ...int) { + e.EffectNode.SetArgs(t, a...) + e.CanStack(false) + e.Duration(-1) +} + // Effect 2276: 将对方场下对自身克制系数最高的一只非狩猎之精灵标记为狩猎之的 type Effect2276 struct{ node.EffectNode } func (e *Effect2276) Skill_Use() bool { - eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2276) - if eff != nil { - e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) + target := pickBenchByCounter227x(e.Ctx().Our, e.Ctx().Opp, true) + if target == nil { + return true } + applyHuntMark227x(e.Ctx().Our, e.Ctx().Opp, 2276, target.Info.CatchTime) return true } type Effect2276Sub struct{ node.EffectNode } +func (e *Effect2276Sub) SetArgs(t *input.Input, a ...int) { + e.EffectNode.SetArgs(t, a...) + e.CanStack(false) + e.Duration(-1) +} + // Effect 2277: 将对方场下对自身克制系数最低的一只非狩猎之精灵标记为狩猎之的 type Effect2277 struct{ node.EffectNode } func (e *Effect2277) Skill_Use() bool { - eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2277) - if eff != nil { - e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) + target := pickBenchByCounter227x(e.Ctx().Our, e.Ctx().Opp, false) + if target == nil { + return true } + applyHuntMark227x(e.Ctx().Our, e.Ctx().Opp, 2277, target.Info.CatchTime) return true } type Effect2277Sub struct{ node.EffectNode } +func (e *Effect2277Sub) SetArgs(t *input.Input, a ...int) { + e.EffectNode.SetArgs(t, a...) + e.CanStack(false) + e.Duration(-1) +} + // Effect 2278: 若对手为狩猎之的则增伤,否则回合内无法主动攻击并附加等量于狩猎之的体力n%的真实伤害 type Effect2278 struct{ node.EffectNode } @@ -232,28 +406,50 @@ func (e *Effect2278) Damage_Mul(zone *info.DamageZone) bool { if zone == nil || zone.Type != info.DamageType.Red || len(e.Args()) < 3 { return true } - if e.Ctx().Opp.GetEffect(input.EffectType.Sub, 2275) != nil || - e.Ctx().Opp.GetEffect(input.EffectType.Sub, 2276) != nil || - e.Ctx().Opp.GetEffect(input.EffectType.Sub, 2277) != nil { - zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(100 + int64(e.Args()[0].IntPart()))).Div(alpacadecimal.NewFromInt(100)) + extra := int64(0) + if huntSoulStack227x(e.Ctx().Our) > 0 { + extra = e.Args()[2].IntPart() + } + if currentIsMarked227x(e.Ctx().Opp) { + bonus := e.Args()[0].IntPart() + extra + zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(100 + bonus)).Div(hundred) return true } - e.Ctx().Opp.AddEffect(e.Ctx().Our, e.Ctx().Our.InitEffect(input.EffectType.Sub, 2278, int(e.Args()[1].IntPart()))) - zone.Damage = zone.Damage.Add(e.Ctx().Opp.CurrentPet.GetMaxHP().Mul(e.Args()[1]).Div(alpacadecimal.NewFromInt(100))) + zone.Damage = alpacadecimal.Zero return true } func (e *Effect2278) Skill_Use() bool { - if len(e.Args()) < 3 { + if len(e.Args()) < 3 || e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS { return true } - if e.Ctx().Opp.GetEffect(input.EffectType.Sub, 2275) == nil && - e.Ctx().Opp.GetEffect(input.EffectType.Sub, 2276) == nil && - e.Ctx().Opp.GetEffect(input.EffectType.Sub, 2277) == nil { - eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2278, int(e.Args()[1].IntPart())) - if eff != nil { - e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) - } + if currentIsMarked227x(e.Ctx().Opp) { + return true + } + + extra := int64(0) + if huntSoulStack227x(e.Ctx().Our) > 0 { + extra = e.Args()[2].IntPart() + } + percent := e.Args()[1].IntPart() + extra + if percent <= 0 { + return true + } + + marked := markedPet227x(e.Ctx().Opp) + if marked == nil { + marked = e.Ctx().Opp.CurrentPet + } + if marked == nil { + return true + } + + damage := marked.GetHP().Mul(alpacadecimal.NewFromInt(percent)).Div(hundred) + if damage.Cmp(alpacadecimal.Zero) > 0 { + e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{ + Type: info.DamageType.Percent, + Damage: damage, + }) } return true } @@ -291,61 +487,167 @@ func (e *Effect2278Sub) ActionStart(a, b *action.SelectSkillAction) bool { type Effect2279 struct{ node.EffectNode } func (e *Effect2279) OnSkill() bool { - eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2279) + if e.Ctx().Our.GetEffect(input.EffectType.Sub, 2279) != nil { + return true + } + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2279, e.SideEffectArgs...) if eff != nil { e.Ctx().Our.AddEffect(e.Ctx().Our, eff) } return true } -type Effect2279Sub struct{ node.EffectNode } +type Effect2279Sub struct { + node.EffectNode + pendingMark bool +} + +func (e *Effect2279Sub) SetArgs(t *input.Input, a ...int) { + e.EffectNode.SetArgs(t, a...) + e.CanStack(false) + e.Duration(-1) +} + +func (e *Effect2279Sub) SwitchOut(in *input.Input) bool { + if in != e.Ctx().Opp || e.Ctx().Opp == nil || e.Ctx().Opp.CurrentPet == nil || e.Ctx().Opp.CurrentPet.Alive() { + return true + } + if !isCatchTimeMarked227x(e.Ctx().Opp, e.Ctx().Opp.CurrentPet.Info.CatchTime) { + return true + } + e.pendingMark = true + e.Stack(e.Stack() + 1) + return true +} + +func (e *Effect2279Sub) SwitchIn(in *input.Input) bool { + if !e.pendingMark || in != e.Ctx().Opp || e.Ctx().Opp == nil || e.Ctx().Opp.CurrentPet == nil || !e.Ctx().Opp.CurrentPet.Alive() { + return true + } + applyHuntMark227x(e.Ctx().Our, e.Ctx().Opp, 2275, e.Ctx().Opp.CurrentPet.Info.CatchTime) + e.pendingMark = false + return true +} + +func (e *Effect2279Sub) Damage_Mul(zone *info.DamageZone) bool { + if zone == nil || zone.Type != info.DamageType.Red || e.Stack() <= 0 || len(e.Args()) == 0 { + return true + } + bonus := int64(e.Stack()) * e.Args()[0].IntPart() + if bonus > 0 { + zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(100 + bonus)).Div(hundred) + } + e.Stack(0) + return true +} // Effect 2280: 技能无效时,对手下回合无法主动切换精灵 type Effect2280 struct{ node.EffectNode } func (e *Effect2280) Skill_Use_ex() bool { - eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2280) + if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.AttackTime != 0 { + return true + } + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2280, 1) if eff != nil { e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) } return true } -type Effect2280Sub struct{ node.EffectNode } +type Effect2280Sub struct{ RoundEffectArg0Base } func (e *Effect2280Sub) SwitchOut(in *input.Input) bool { - if in == e.Ctx().Opp { - return false + if in != e.Ctx().Our { + return true } - return true + e.Alive(false) + return false } // Effect 2281: 若先出手则自身下次受到致死伤害时保留1点体力 type Effect2281 struct{ node.EffectNode } -func (e *Effect2281) DamageLockEx(zone *info.DamageZone) bool { - if zone == nil || zone.Type != info.DamageType.Red || !e.IsFirst() { +func (e *Effect2281) Skill_Use() bool { + if !e.IsFirst() { return true } - if zone.Damage.Cmp(e.Ctx().Our.CurrentPet.GetHP()) >= 0 { - zone.Damage = e.Ctx().Our.CurrentPet.GetHP().Sub(alpacadecimal.NewFromInt(1)) + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2281) + if eff != nil { + e.Ctx().Our.AddEffect(e.Ctx().Our, eff) } return true } +type Effect2281Sub struct{ FixedDurationNeg1Base } + +func (e *Effect2281Sub) DamageLockEx(zone *info.DamageZone) bool { + if zone == nil || zone.Type != info.DamageType.Red || e.Ctx().Our == nil || e.Ctx().Our.CurrentPet == nil { + return true + } + currentHP := e.Ctx().Our.CurrentPet.GetHP() + if zone.Damage.Cmp(currentHP) < 0 { + return true + } + zone.Damage = currentHP.Sub(alpacadecimal.NewFromInt(1)) + e.Alive(false) + return true +} + // Effect 2282: {0}回合内使用技能恢复自身最大体力1/{1}并造成等量百分比伤害 type Effect2282 struct{ node.EffectNode } -func (e *Effect2282) OnSkill() bool { - if len(e.Args()) < 3 { +func applyEffect2282(owner, target *input.Input, divisor, truePercent int) { + if owner == nil || target == nil || owner.CurrentPet == nil || target.CurrentPet == nil || divisor <= 0 { + return + } + + heal := owner.CurrentPet.GetMaxHP().Div(alpacadecimal.NewFromInt(int64(divisor))) + if heal.Cmp(alpacadecimal.Zero) <= 0 { + return + } + + owner.Heal(owner, &action.SelectSkillAction{}, heal) + beforeHP := target.CurrentPet.GetHP() + target.Damage(owner, &info.DamageZone{Type: info.DamageType.Percent, Damage: heal}) + if truePercent <= 0 || beforeHP.Cmp(target.CurrentPet.GetHP()) != 0 { + return + } + + trueDamage := owner.CurrentPet.GetMaxHP().Mul(alpacadecimal.NewFromInt(int64(truePercent))).Div(hundred) + if trueDamage.Cmp(alpacadecimal.Zero) > 0 { + target.Damage(owner, &info.DamageZone{Type: info.DamageType.True, Damage: trueDamage}) + } +} + +func (e *Effect2282) Skill_Use() bool { + if len(e.Args()) < 3 || e.Ctx().SkillEntity == nil { return true } - heal := e.Ctx().Our.CurrentPet.GetMaxHP().Div(e.Args()[1]) - e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, heal) - e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Percent, Damage: heal}) - if e.Ctx().Opp.CurrentPet.GetHP().Cmp(e.Ctx().Opp.CurrentPet.GetMaxHP()) == 0 { - e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.True, Damage: e.Ctx().Opp.CurrentPet.GetMaxHP().Mul(e.Args()[2]).Div(alpacadecimal.NewFromInt(100))}) + + rounds := int(e.Args()[0].IntPart()) + divisor := int(e.Args()[1].IntPart()) + truePercent := int(e.Args()[2].IntPart()) + + applyEffect2282(e.Ctx().Our, e.Ctx().Opp, divisor, truePercent) + if rounds <= 1 { + return true } + + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2282, rounds-1, divisor, truePercent) + if eff != nil { + e.Ctx().Our.AddEffect(e.Ctx().Our, eff) + } + return true +} + +type Effect2282Sub struct{ RoundEffectArg0Base } + +func (e *Effect2282Sub) Skill_Use() bool { + if len(e.Args()) < 3 || e.Ctx().SkillEntity == nil { + return true + } + applyEffect2282(e.Ctx().Our, e.Ctx().Opp, int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart())) return true } @@ -353,16 +655,60 @@ func (e *Effect2282) OnSkill() bool { type Effect2283 struct{ node.EffectNode } func (e *Effect2283) Skill_Use() bool { - shield := e.Ctx().Our.CurrentShield() - if shield.Cmp(alpacadecimal.Zero) <= 0 { + if len(e.Args()) < 3 || e.Ctx().Our == nil || e.Ctx().Our.CurrentPet == nil { return true } - heal := shield.Div(alpacadecimal.NewFromInt(2)) + + consumed := e.Ctx().Our.ConsumeAllShield() + if consumed.Cmp(alpacadecimal.Zero) <= 0 { + return true + } + + e.Ctx().Our.Damage(e.Ctx().Our, &info.DamageZone{ + Type: info.DamageType.True, + Damage: consumed, + }) + + benchCount := 0 for _, pet := range e.Ctx().Our.AllPet { - if pet == nil || pet == e.Ctx().Our.CurrentPet || pet.Alive() { + if pet == nil || pet == e.Ctx().Our.CurrentPet || pet.Info.Hp == 0 { continue } - pet.Info.Hp += uint32(heal.IntPart()) + benchCount++ + } + if benchCount > 0 { + healEach := consumed.Div(alpacadecimal.NewFromInt(int64(benchCount))) + if healEach.Cmp(alpacadecimal.Zero) > 0 { + for _, pet := range e.Ctx().Our.AllPet { + if pet == nil || pet == e.Ctx().Our.CurrentPet || pet.Info.Hp == 0 { + continue + } + pet.Info.ModelHP(healEach.IntPart()) + } + } + } + + if consumed.Cmp(e.Args()[0]) <= 0 { + return true + } + + clearedTotal := consumed + if e.Ctx().Opp != nil { + clearedTotal = clearedTotal.Add(e.Ctx().Opp.ConsumeAllShield()) + } + + bonusShield := clearedTotal.Mul(e.Args()[1]).Div(hundred) + if bonusShield.Cmp(alpacadecimal.Zero) <= 0 { + return true + } + if e.Args()[2].Cmp(alpacadecimal.Zero) > 0 { + maxBonus := e.Ctx().Our.CurrentPet.GetMaxHP().Mul(e.Args()[2]).Div(hundred) + if maxBonus.Cmp(alpacadecimal.Zero) > 0 && bonusShield.Cmp(maxBonus) > 0 { + bonusShield = maxBonus + } + } + if bonusShield.Cmp(alpacadecimal.Zero) > 0 { + e.Ctx().Our.AddShield(bonusShield) } return true } @@ -370,13 +716,108 @@ func (e *Effect2283) Skill_Use() bool { // Effect 2284: 剥夺对手系别并附加控制类异常与系别伤害 type Effect2284 struct{ node.EffectNode } -func (e *Effect2284) Skill_Use() bool { return true } +func addRandomControlStatus2284(owner, target *input.Input, count int) { + if owner == nil || target == nil || count <= 0 { + return + } + statuses := []int{ + int(info.PetStatus.Paralysis), + int(info.PetStatus.Fear), + int(info.PetStatus.Tired), + int(info.PetStatus.Petrified), + int(info.PetStatus.Sleep), + } + if count > len(statuses) { + count = len(statuses) + } + for _, idx := range grand.Perm(len(statuses))[:count] { + addStatus2270(owner, target, statuses[idx]) + } +} -// Effect 2285: {0}回合内对手无法通过技能恢复体力 -type Effect2285 struct{ node.EffectNode } +func (e *Effect2284) Skill_Use() bool { + if len(e.Args()) < 2 || e.Ctx().Opp == nil || e.Ctx().Opp.CurrentPet == nil { + return true + } + if e.Ctx().Opp.CurrentPet.PetInfo.Type == int(element.ElementTypeNormal) { + return true + } + + strippedTypes := 1 + e.Ctx().Opp.CurrentPet.PetInfo.Type = int(element.ElementTypeNormal) + addRandomControlStatus2284(e.Ctx().Our, e.Ctx().Opp, strippedTypes) + + rounds := int(e.Args()[0].IntPart()) + damagePerType := int(e.Args()[1].IntPart()) + if rounds <= 0 || damagePerType <= 0 { + return true + } + + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2284, rounds, damagePerType, strippedTypes) + if eff != nil { + e.Ctx().Our.AddEffect(e.Ctx().Our, eff) + } + return true +} + +type Effect2284Sub struct{ RoundEffectArg0Base } + +func (e *Effect2284Sub) Action_end() bool { + if len(e.Args()) < 3 || e.Ctx().SkillEntity == nil || e.Ctx().Opp == nil || e.Ctx().Opp.CurrentPet == nil { + return true + } + + activeStatus := countAliveStatusKinds(e.Ctx().Opp) + if activeStatus <= 0 { + return true + } + + maxTriggers := int(e.Args()[2].IntPart()) + if maxTriggers <= 0 { + return true + } + if activeStatus > maxTriggers { + activeStatus = maxTriggers + } + + damage := e.Args()[1].Mul(alpacadecimal.NewFromInt(int64(activeStatus))) + 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 2285: {0}回合内对手无法通过技能恢复体力;若对手当回合未选择技能则改为{1}回合内对手体力恢复效果减少{2}% +type Effect2285 struct { + node.EffectNode + oppSelectedSkill bool +} + +func (e *Effect2285) ComparePre(fattack, sattack *action.SelectSkillAction) bool { + current := actionByPlayer(fattack, sattack, e.Ctx().Opp.UserID) + e.oppSelectedSkill = current != nil && current.SkillEntity != nil + return true +} func (e *Effect2285) Skill_Use() bool { - eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2285, e.SideEffectArgs...) + if len(e.Args()) < 3 { + return true + } + + rounds := int(e.Args()[0].IntPart()) + reduce := 0 + mode := 0 // 0: block skill-heal; 1: reduce all healing + if !e.oppSelectedSkill { + rounds = int(e.Args()[1].IntPart()) + reduce = int(e.Args()[2].IntPart()) + mode = 1 + } + if rounds <= 0 { + return true + } + + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2285, rounds, reduce, mode) if eff != nil { e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) } @@ -385,8 +826,26 @@ func (e *Effect2285) Skill_Use() bool { type Effect2285Sub struct{ RoundEffectArg0Base } -func (e *Effect2285Sub) Heal_Pre(_ action.BattleActionI, amount *int) bool { - if amount != nil { +func (e *Effect2285Sub) Heal_Pre(ac action.BattleActionI, amount *int) bool { + if amount == nil || *amount <= 0 { + return true + } + + // Fallback mode: reduce healing directly. + if len(e.Args()) >= 3 && e.Args()[2].IntPart() == 1 { + reduce := int(e.Args()[1].IntPart()) + if reduce < 0 { + reduce = 0 + } + if reduce > 100 { + reduce = 100 + } + *amount = *amount * (100 - reduce) / 100 + return true + } + + // Default mode: only block healing caused by skill actions. + if _, ok := ac.(*action.SelectSkillAction); ok { *amount = 0 } return true @@ -395,25 +854,126 @@ func (e *Effect2285Sub) Heal_Pre(_ action.BattleActionI, amount *int) bool { // Effect 2286: 对手n次回合类效果、能力提升效果被消除时体力上限减少 type Effect2286 struct{ node.EffectNode } -func (e *Effect2286) Skill_Use() bool { return true } +func (e *Effect2286) Skill_Use() bool { + if len(e.Args()) < 2 { + return true + } + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2286, e.SideEffectArgs...) + if eff != nil { + e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) + } + return true +} + +type Effect2286Sub struct { + node.EffectNode + remaining int + reduceMax int +} + +func (e *Effect2286Sub) 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.reduceMax = a[1] + } +} + +func (e *Effect2286Sub) PropBefer(_ *input.Input, prop, level int8) bool { + if e.remaining <= 0 || e.Ctx().Our == nil || e.Ctx().Our.CurrentPet == nil { + e.Alive(false) + return true + } + if level != 0 || prop < 0 || int(prop) >= len(e.Ctx().Our.Prop) || e.Ctx().Our.Prop[prop] <= 0 { + return true + } + if e.reduceMax > 0 { + oldMax := int(e.Ctx().Our.CurrentPet.Info.MaxHp) + if oldMax > 1 { + newMax := oldMax - e.reduceMax + if newMax < 1 { + newMax = 1 + } + e.Ctx().Our.CurrentPet.Info.MaxHp = uint32(newMax) + if e.Ctx().Our.CurrentPet.Info.Hp > e.Ctx().Our.CurrentPet.Info.MaxHp { + e.Ctx().Our.CurrentPet.Info.Hp = e.Ctx().Our.CurrentPet.Info.MaxHp + } + } + } + e.remaining-- + if e.remaining <= 0 { + e.Alive(false) + } + return true +} // Effect 2287: {0}回合内使用技能恢复自身最大体力1/{1}并造成等量百分比伤害,溢出转化为护罩 type Effect2287 struct{ node.EffectNode } -func (e *Effect2287) OnSkill() bool { +func applyEffect2287(owner, target *input.Input, divisor int) { + if owner == nil || owner.CurrentPet == nil || target == nil || target.CurrentPet == nil || divisor <= 0 { + return + } + + heal := owner.CurrentPet.GetMaxHP().Div(alpacadecimal.NewFromInt(int64(divisor))) + if heal.Cmp(alpacadecimal.Zero) <= 0 { + return + } + + before := owner.CurrentPet.GetHP() + owner.Heal(owner, &action.SelectSkillAction{}, heal) + after := owner.CurrentPet.GetHP() + actual := after.Sub(before) + if actual.Cmp(alpacadecimal.Zero) < 0 { + actual = alpacadecimal.Zero + } + overflow := heal.Sub(actual) + if overflow.Cmp(alpacadecimal.Zero) > 0 { + owner.AddShield(overflow) + } + + target.Damage(owner, &info.DamageZone{Type: info.DamageType.Percent, Damage: heal}) +} + +func (e *Effect2287) Skill_Use() bool { if len(e.Args()) < 2 { return true } - heal := e.Ctx().Our.CurrentPet.GetMaxHP().Div(e.Args()[1]) - e.Ctx().Our.Heal(e.Ctx().Our, &action.SelectSkillAction{}, heal) - e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Percent, Damage: heal}) + + rounds := int(e.Args()[0].IntPart()) + divisor := int(e.Args()[1].IntPart()) + applyEffect2287(e.Ctx().Our, e.Ctx().Opp, divisor) + if rounds <= 1 { + return true + } + + eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2287, rounds-1, divisor) + if eff != nil { + e.Ctx().Our.AddEffect(e.Ctx().Our, eff) + } return true } -// Effect 2288: 技能无效时,为对手附加3回合的炙焰仙蕊 +type Effect2287Sub struct{ RoundEffectArg0Base } + +func (e *Effect2287Sub) Skill_Use() bool { + if len(e.Args()) < 2 { + return true + } + applyEffect2287(e.Ctx().Our, e.Ctx().Opp, int(e.Args()[1].IntPart())) + return true +} + +// Effect 2288: 技能无效时,为对手附加3回合的琼花仙荼 type Effect2288 struct{ node.EffectNode } func (e *Effect2288) Skill_Use_ex() bool { + if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.AttackTime != 0 { + return true + } eff := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2288, 3) if eff != nil { e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) @@ -421,44 +981,334 @@ func (e *Effect2288) Skill_Use_ex() bool { return true } -type Effect2288Sub struct{ node.EffectNode } +type Effect2288Sub struct{ RoundEffectArg0Base } -// Effect 2289: 自身每点雷霆威力提升{0}点 +func (e *Effect2288Sub) ActionStartEx(fattack, sattack *action.SelectSkillAction) bool { + if e.Ctx().Our == nil || e.Ctx().Our.CurrentPet == nil { + return true + } + damage := e.Ctx().Our.CurrentPet.GetMaxHP().Div(alpacadecimal.NewFromInt(6)) + if damage.Cmp(alpacadecimal.Zero) > 0 { + source := e.Ctx().Opp + if source == nil { + source = e.Ctx().Our + } + e.Ctx().Our.Damage(source, &info.DamageZone{Type: info.DamageType.True, Damage: damage}) + } + return true +} + +func (e *Effect2288Sub) Heal_Pre(ac action.BattleActionI, amount *int) bool { + if amount == nil || *amount == 0 { + return true + } + if _, ok := ac.(*action.UseItemAction); ok && *amount > 0 { + *amount = -*amount + } + if *amount <= 0 { + return true + } + source := e.Ctx().Opp + if source == nil { + source = e.Ctx().Our + } + poison := source.InitEffect(input.EffectType.Status, int(info.PetStatus.Poisoned)) + if poison != nil { + poison.Duration(2) + e.Ctx().Our.AddEffect(source, poison) + } + return true +} + +// Effect 2289: 自身每点亮1颗雷辰威力提升{0}点;若未点亮雷辰则每存活1只己方精灵伤害不低于{1} type Effect2289 struct{ node.EffectNode } -func (e *Effect2289) SkillHit() bool { return true } +func (e *Effect2289) SkillHit() bool { + if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || len(e.Args()) == 0 { + return true + } + energy := e.Ctx().Our.CurrentDivineEnergy() + if energy <= 0 { + return true + } + e.Ctx().SkillEntity.XML.Power += energy * int(e.Args()[0].IntPart()) + return true +} + +func (e *Effect2289) DamageFloor(zone *info.DamageZone) bool { + if zone == nil || zone.Type != info.DamageType.Red || len(e.Args()) < 2 { + return true + } + if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || e.Ctx().SkillEntity.AttackTime == 0 { + return true + } + if e.Ctx().Our.CurrentDivineEnergy() > 0 { + return true + } + + aliveCount := 0 + for _, pet := range e.Ctx().Our.AllPet { + if pet != nil && pet.Alive() { + aliveCount++ + } + } + if aliveCount <= 0 { + return true + } + + floor := e.Args()[1].Mul(alpacadecimal.NewFromInt(int64(aliveCount))) + if zone.Damage.Cmp(floor) < 0 { + zone.Damage = floor + } + return true +} // Effect 2290: 自身每点雷霆全属性+1 type Effect2290 struct{ node.EffectNode } -func (e *Effect2290) SkillHit() bool { return true } +func boostAllProps2294(owner *input.Input, level int) { + if owner == nil || level == 0 { + return + } + if level > 0 { + for step := 0; step < level; step++ { + for i := range owner.Prop[:] { + owner.SetProp(owner, int8(i), 1) + } + } + return + } + for step := 0; step < -level; step++ { + for i := range owner.Prop[:] { + owner.SetProp(owner, int8(i), -1) + } + } +} + +func getMartialState2294(owner *input.Input, create bool) *Effect2294Sub { + if owner == nil { + return nil + } + if eff := owner.GetEffect(input.EffectType.Sub, 2294); eff != nil { + if state, ok := eff.(*Effect2294Sub); ok { + if state.track == 0 { + state.track = 1 + } + return state + } + } + if !create { + return nil + } + eff := owner.InitEffect(input.EffectType.Sub, 2294) + if eff == nil { + return nil + } + owner.AddEffect(owner, eff) + state, ok := eff.(*Effect2294Sub) + if !ok { + return nil + } + if state.track == 0 { + state.track = 1 + } + return state +} + +func (e *Effect2290) SkillHit() bool { + if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS { + return true + } + state := getMartialState2294(e.Ctx().Our, false) + if state == nil { + return true + } + energy := e.Ctx().Our.CurrentDivineEnergy() + if energy <= state.appliedLightning { + return true + } + delta := energy - state.appliedLightning + boostAllProps2294(e.Ctx().Our, delta) + state.appliedLightning = energy + return true +} // Effect 2291: 自身武道衔化轨迹为正转时... type Effect2291 struct{ node.EffectNode } -func (e *Effect2291) OnSkill() bool { return true } +type Effect2291Sub struct { + node.EffectNode + mode int + reduce int +} + +func (e *Effect2291Sub) SetArgs(t *input.Input, a ...int) { + e.EffectNode.SetArgs(t, a...) + e.Duration(-1) + e.mode = 1 + e.reduce = 0 + if len(a) > 0 { + e.Duration(a[0]) + } + if len(a) > 1 { + e.reduce = a[1] + } + if len(a) > 2 { + e.mode = a[2] + } + if e.mode == 0 { + e.mode = 1 + } + if e.reduce < 0 { + e.reduce = 0 + } + if e.reduce > 100 { + e.reduce = 100 + } +} + +func (e *Effect2291Sub) Heal_Pre(ac action.BattleActionI, value *int) bool { + if e.mode != 1 || value == nil || *value <= 0 { + return true + } + if _, ok := ac.(*action.SelectSkillAction); !ok || ac.GetPlayerID() != e.Ctx().Our.UserID { + return true + } + *value = 0 + return true +} + +func (e *Effect2291Sub) Damage_Mul(zone *info.DamageZone) bool { + if e.mode != -1 || e.reduce <= 0 || zone == nil || zone.Type != info.DamageType.Red { + return true + } + zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(int64(100 - e.reduce))).Div(alpacadecimal.NewFromInt(100)) + return true +} + +func (e *Effect2291) OnSkill() bool { + if len(e.Args()) < 3 { + return true + } + state := getMartialState2294(e.Ctx().Our, true) + if state == nil { + return true + } + if state.track < 0 { + duration := int(e.Args()[1].IntPart()) + sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2291, duration, int(e.Args()[2].IntPart()), -1) + if sub != nil { + e.Ctx().Opp.AddEffect(e.Ctx().Our, sub) + } + return true + } + duration := int(e.Args()[0].IntPart()) + sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 2291, duration, 0, 1) + if sub != nil { + e.Ctx().Opp.AddEffect(e.Ctx().Our, sub) + } + return true +} // Effect 2292: 将自身武道衔化轨迹改为逆转并反弹武道正转期间吸收的伤害 type Effect2292 struct{ node.EffectNode } -func (e *Effect2292) OnSkill() bool { return true } +func (e *Effect2292) OnSkill() bool { + if len(e.Args()) == 0 { + return true + } + state := getMartialState2294(e.Ctx().Our, true) + if state == nil { + return true + } + state.track = -1 + capDamage := e.Ctx().Our.CurrentPet.GetMaxHP().Mul(e.Args()[0]).Div(alpacadecimal.NewFromInt(100)) + if capDamage.Cmp(alpacadecimal.Zero) > 0 { + damage := state.blockedDamage + if damage.Cmp(capDamage) > 0 { + damage = capDamage + } + if damage.Cmp(alpacadecimal.Zero) > 0 { + e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{Type: info.DamageType.Fixed, Damage: damage}) + } + } + state.blockedDamage = alpacadecimal.Zero + return true +} // Effect 2293: 复原自身的武道衔化轨迹,每n次衔化时全属性+1 type Effect2293 struct{ node.EffectNode } -func (e *Effect2293) Skill_Use() bool { return true } +func (e *Effect2293) Skill_Use() bool { + if len(e.Args()) == 0 { + return true + } + state := getMartialState2294(e.Ctx().Our, true) + if state == nil { + return true + } + state.track = 1 + state.pendingBoost += int(e.Args()[0].IntPart()) + if state.pendingBoost < 0 { + state.pendingBoost = 0 + } + state.blockedDamage = alpacadecimal.Zero + return true +} // Effect 2294: 衔化自身的武道 type Effect2294 struct{ node.EffectNode } -func (e *Effect2294) Skill_Use() bool { return true } +type Effect2294Sub struct { + node.EffectNode + track int + lightning int + appliedLightning int + pendingBoost int + blockedDamage alpacadecimal.Decimal +} + +func (e *Effect2294Sub) SetArgs(t *input.Input, a ...int) { + e.EffectNode.SetArgs(t, a...) + e.Duration(-1) + if e.track == 0 { + e.track = 1 + } +} + +func (e *Effect2294Sub) Damage_Shield(zone *info.DamageZone) bool { + if zone == nil || zone.Type != info.DamageType.Red || zone.Damage.Cmp(alpacadecimal.Zero) <= 0 || e.track <= 0 { + return true + } + shield := e.Ctx().Our.CurrentShield() + if shield.Cmp(alpacadecimal.Zero) <= 0 { + return true + } + blocked := alpacadecimal.Min(shield, zone.Damage) + if blocked.Cmp(alpacadecimal.Zero) > 0 { + e.blockedDamage = e.blockedDamage.Add(blocked) + } + return true +} + +func (e *Effect2294) Skill_Use() bool { + state := getMartialState2294(e.Ctx().Our, true) + if state == nil { + return true + } + state.lightning++ + if state.pendingBoost > 0 { + boostAllProps2294(e.Ctx().Our, 1) + state.pendingBoost-- + } + return true +} func init() { input.InitEffect(input.EffectType.Skill, 2270, &Effect2270{}) input.InitEffect(input.EffectType.Sub, 2270, &Effect2270Sub{}) input.InitEffect(input.EffectType.Skill, 2271, &Effect2271{}) input.InitEffect(input.EffectType.Sub, 2271, &Effect2271Sub{}) - input.InitEffect(input.EffectType.Sub, 2271, &Effect2271CritSub{}) input.InitEffect(input.EffectType.Skill, 2272, &Effect2272{}) input.InitEffect(input.EffectType.Skill, 2273, &Effect2273{}) input.InitEffect(input.EffectType.Skill, 2274, &Effect2274{}) @@ -475,18 +1325,26 @@ func init() { input.InitEffect(input.EffectType.Skill, 2280, &Effect2280{}) input.InitEffect(input.EffectType.Sub, 2280, &Effect2280Sub{}) input.InitEffect(input.EffectType.Skill, 2281, &Effect2281{}) + input.InitEffect(input.EffectType.Sub, 2281, &Effect2281Sub{}) input.InitEffect(input.EffectType.Skill, 2282, &Effect2282{}) + input.InitEffect(input.EffectType.Sub, 2282, &Effect2282Sub{}) input.InitEffect(input.EffectType.Skill, 2283, &Effect2283{}) input.InitEffect(input.EffectType.Skill, 2284, &Effect2284{}) + input.InitEffect(input.EffectType.Sub, 2284, &Effect2284Sub{}) input.InitEffect(input.EffectType.Skill, 2285, &Effect2285{}) + input.InitEffect(input.EffectType.Sub, 2285, &Effect2285Sub{}) input.InitEffect(input.EffectType.Skill, 2286, &Effect2286{}) + input.InitEffect(input.EffectType.Sub, 2286, &Effect2286Sub{}) input.InitEffect(input.EffectType.Skill, 2287, &Effect2287{}) + input.InitEffect(input.EffectType.Sub, 2287, &Effect2287Sub{}) input.InitEffect(input.EffectType.Skill, 2288, &Effect2288{}) input.InitEffect(input.EffectType.Sub, 2288, &Effect2288Sub{}) input.InitEffect(input.EffectType.Skill, 2289, &Effect2289{}) input.InitEffect(input.EffectType.Skill, 2290, &Effect2290{}) input.InitEffect(input.EffectType.Skill, 2291, &Effect2291{}) + input.InitEffect(input.EffectType.Sub, 2291, &Effect2291Sub{}) input.InitEffect(input.EffectType.Skill, 2292, &Effect2292{}) input.InitEffect(input.EffectType.Skill, 2293, &Effect2293{}) input.InitEffect(input.EffectType.Skill, 2294, &Effect2294{}) + input.InitEffect(input.EffectType.Sub, 2294, &Effect2294Sub{}) } diff --git a/login/internal/cmd/robot_group_config.go b/login/internal/cmd/robot_group_config.go new file mode 100644 index 000000000..5e70bb357 --- /dev/null +++ b/login/internal/cmd/robot_group_config.go @@ -0,0 +1,172 @@ +package cmd + +import ( + "encoding/json" + "os" + "path/filepath" + "sort" + "sync" +) + +const ( + robotGroupConfigFile = "robot_groups.json" + robotBlacklistFile = "robot_blacklist.json" +) + +type robotGroupConfig struct { + NotifyGroupIDs []int64 `json:"notify_group_ids"` + ManageGroupIDs []int64 `json:"manage_group_ids"` +} + +type robotBlacklist struct { + UserIDs []int64 `json:"user_ids"` +} + +var robotBlacklistMu sync.Mutex + +func loadRobotGroupConfig() robotGroupConfig { + path := resolveRobotConfigPath(robotGroupConfigFile) + ensureRobotConfigFile(path, []byte("{\n \"notify_group_ids\": [],\n \"manage_group_ids\": []\n}\n")) + + var cfg robotGroupConfig + data, err := os.ReadFile(path) + if err != nil { + return cfg + } + if err = json.Unmarshal(data, &cfg); err != nil { + return robotGroupConfig{} + } + cfg.NotifyGroupIDs = normalizeInt64IDs(cfg.NotifyGroupIDs) + cfg.ManageGroupIDs = normalizeInt64IDs(cfg.ManageGroupIDs) + return cfg +} + +func loadRobotBlacklist() robotBlacklist { + path := resolveRobotConfigPath(robotBlacklistFile) + ensureRobotConfigFile(path, []byte("{\n \"user_ids\": []\n}\n")) + + var cfg robotBlacklist + data, err := os.ReadFile(path) + if err != nil { + return cfg + } + if err = json.Unmarshal(data, &cfg); err != nil { + return robotBlacklist{} + } + cfg.UserIDs = normalizeInt64IDs(cfg.UserIDs) + return cfg +} + +func saveRobotBlacklist(cfg robotBlacklist) error { + path := resolveRobotConfigPath(robotBlacklistFile) + cfg.UserIDs = normalizeInt64IDs(cfg.UserIDs) + ensureRobotConfigFile(path, []byte("{\n \"user_ids\": []\n}\n")) + + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + data = append(data, '\n') + return os.WriteFile(path, data, 0o644) +} + +func addRobotBlacklistUser(userID int64) error { + if userID <= 0 { + return nil + } + + robotBlacklistMu.Lock() + defer robotBlacklistMu.Unlock() + + cfg := loadRobotBlacklist() + for _, id := range cfg.UserIDs { + if id == userID { + return nil + } + } + cfg.UserIDs = append(cfg.UserIDs, userID) + return saveRobotBlacklist(cfg) +} + +func removeRobotBlacklistUser(userID int64) error { + if userID <= 0 { + return nil + } + + robotBlacklistMu.Lock() + defer robotBlacklistMu.Unlock() + + cfg := loadRobotBlacklist() + ids := make([]int64, 0, len(cfg.UserIDs)) + for _, id := range cfg.UserIDs { + if id != userID { + ids = append(ids, id) + } + } + cfg.UserIDs = ids + return saveRobotBlacklist(cfg) +} + +func isRobotBlacklisted(userID int64) bool { + if userID <= 0 { + return false + } + cfg := loadRobotBlacklist() + for _, id := range cfg.UserIDs { + if id == userID { + return true + } + } + return false +} + +func resolveRobotConfigPath(name string) string { + candidates := []string{ + filepath.Join("public", "config", name), + filepath.Join("..", "public", "config", name), + filepath.Join("..", "..", "public", "config", name), + } + + for _, candidate := range candidates { + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + return candidates[0] +} + +func ensureRobotConfigFile(path string, fallback []byte) { + if _, err := os.Stat(path); err == nil { + return + } + + _ = os.MkdirAll(filepath.Dir(path), 0o755) + _ = os.WriteFile(path, fallback, 0o644) +} + +func normalizeInt64IDs(ids []int64) []int64 { + uniq := make(map[int64]struct{}, len(ids)) + for _, id := range ids { + if id > 0 { + uniq[id] = struct{}{} + } + } + + res := make([]int64, 0, len(uniq)) + for id := range uniq { + res = append(res, id) + } + sort.Slice(res, func(i, j int) bool { + return res[i] < res[j] + }) + return res +} + +func isConfiguredGroup(groupID int64, groups []int64) bool { + for _, id := range groups { + if id == groupID { + return true + } + } + return false +} diff --git a/login/internal/cmd/robot_group_manage.go b/login/internal/cmd/robot_group_manage.go new file mode 100644 index 000000000..c79bf8ef9 --- /dev/null +++ b/login/internal/cmd/robot_group_manage.go @@ -0,0 +1,212 @@ +package cmd + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +const ( + robotNotifyModeNormal = "普通" + robotNotifyModeAtAll = "艾特全体" + robotNotifyModeEssence = "群精华" +) + +var qqCodePattern = regexp.MustCompile(`qq=(\d+)`) + +func init() { + zero.OnCommand("群通知", zero.SuperUserPermission).Handle(handleRobotGroupNotify) + zero.OnCommand("通知群列表", zero.SuperUserPermission).Handle(handleRobotNotifyGroupList) + zero.OnCommand("全群拉黑", zero.SuperUserPermission).Handle(handleRobotBlacklistAdd) + zero.OnCommand("全群解黑", zero.SuperUserPermission).Handle(handleRobotBlacklistRemove) + zero.OnCommand("黑名单列表", zero.SuperUserPermission).Handle(handleRobotBlacklistList) + + zero.OnRequest().Handle(handleRobotBlacklistRequest) + zero.OnNotice().Handle(handleRobotBlacklistNotice) +} + +func handleRobotGroupNotify(ctx *zero.Ctx) { + cfg := loadRobotGroupConfig() + if len(cfg.NotifyGroupIDs) == 0 { + ctx.Send("未配置通知群,请先编辑 public/config/robot_groups.json 的 notify_group_ids") + return + } + + mode, content := parseNotifyModeAndContent(ctx.Event.Message.String()) + if content == "" { + ctx.Send("用法:/群通知 [普通|艾特全体|群精华] 内容") + return + } + + success := make([]string, 0, len(cfg.NotifyGroupIDs)) + for _, groupID := range cfg.NotifyGroupIDs { + switch mode { + case robotNotifyModeAtAll: + ctx.SendGroupMessage(groupID, message.Message{message.AtAll(), message.Text("\n", content)}) + case robotNotifyModeEssence: + msgID := ctx.SendGroupMessage(groupID, message.Text("【群通知】\n", content)) + if msgID != 0 { + ctx.SetGroupEssenceMessage(msgID) + } + default: + ctx.SendGroupMessage(groupID, message.Text("【群通知】\n", content)) + } + success = append(success, strconv.FormatInt(groupID, 10)) + } + + ctx.Send(fmt.Sprintf("已按[%s]发送群通知,共 %d 个群:%s", mode, len(success), strings.Join(success, ", "))) +} + +func handleRobotNotifyGroupList(ctx *zero.Ctx) { + cfg := loadRobotGroupConfig() + ctx.Send(formatRobotGroups("通知群", cfg.NotifyGroupIDs) + "\n支持模式:普通、艾特全体、群精华") +} + +func handleRobotBlacklistAdd(ctx *zero.Ctx) { + cfg := loadRobotGroupConfig() + if len(cfg.ManageGroupIDs) == 0 { + ctx.Send("未配置管理群,请先编辑 public/config/robot_groups.json 的 manage_group_ids") + return + } + + userID := extractCommandUserID(ctx.Event.Message.String(), "全群拉黑") + if userID == 0 { + ctx.Send("用法:/全群拉黑 @某人 或 /全群拉黑 QQ号") + return + } + if userID == ctx.Event.SelfID { + ctx.Send("不能把机器人自己加入黑名单") + return + } + + if err := addRobotBlacklistUser(userID); err != nil { + ctx.Send("写入黑名单失败:" + err.Error()) + return + } + + for _, groupID := range cfg.ManageGroupIDs { + ctx.SetGroupKick(groupID, userID, true) + } + + ctx.Send(fmt.Sprintf("已将 %d 加入全群黑名单,并在 %d 个管理群执行踢出/拒绝重加", userID, len(cfg.ManageGroupIDs))) +} + +func handleRobotBlacklistRemove(ctx *zero.Ctx) { + userID := extractCommandUserID(ctx.Event.Message.String(), "全群解黑") + if userID == 0 { + ctx.Send("用法:/全群解黑 @某人 或 /全群解黑 QQ号") + return + } + + if err := removeRobotBlacklistUser(userID); err != nil { + ctx.Send("移出黑名单失败:" + err.Error()) + return + } + + ctx.Send(fmt.Sprintf("已将 %d 从全群黑名单移除", userID)) +} + +func handleRobotBlacklistList(ctx *zero.Ctx) { + cfg := loadRobotBlacklist() + if len(cfg.UserIDs) == 0 { + ctx.Send("当前黑名单为空") + return + } + + lines := make([]string, 0, len(cfg.UserIDs)) + for _, userID := range cfg.UserIDs { + lines = append(lines, strconv.FormatInt(userID, 10)) + } + ctx.Send("当前全群黑名单:\n" + strings.Join(lines, "\n")) +} + +func handleRobotBlacklistRequest(ctx *zero.Ctx) { + cfg := loadRobotGroupConfig() + if ctx.Event.RequestType != "group" { + return + } + if !isConfiguredGroup(ctx.Event.GroupID, cfg.ManageGroupIDs) { + return + } + if !isRobotBlacklisted(ctx.Event.UserID) { + return + } + + ctx.SetGroupAddRequest(ctx.Event.Flag, ctx.Event.SubType, false, "该账号已被机器人拉黑") +} + +func handleRobotBlacklistNotice(ctx *zero.Ctx) { + cfg := loadRobotGroupConfig() + if ctx.Event.NoticeType != "group_increase" { + return + } + if !isConfiguredGroup(ctx.Event.GroupID, cfg.ManageGroupIDs) { + return + } + if !isRobotBlacklisted(ctx.Event.UserID) || ctx.Event.UserID == ctx.Event.SelfID { + return + } + + ctx.SetGroupKick(ctx.Event.GroupID, ctx.Event.UserID, true) + ctx.SendGroupMessage(ctx.Event.GroupID, message.Text("已自动移除黑名单成员:", strconv.FormatInt(ctx.Event.UserID, 10))) +} + +func extractCommandBody(raw, command string) string { + text := strings.TrimSpace(raw) + text = strings.TrimPrefix(text, "/") + text = strings.TrimSpace(text) + text = strings.TrimPrefix(text, command) + return strings.TrimSpace(text) +} + +func extractCommandUserID(raw, command string) int64 { + if matches := qqCodePattern.FindStringSubmatch(raw); len(matches) == 2 { + if userID, err := strconv.ParseInt(matches[1], 10, 64); err == nil && userID > 0 { + return userID + } + } + + text := extractCommandBody(raw, command) + for _, field := range strings.Fields(text) { + if userID, err := strconv.ParseInt(field, 10, 64); err == nil && userID > 0 { + return userID + } + } + return 0 +} + +func parseNotifyModeAndContent(raw string) (string, string) { + body := extractCommandBody(raw, "群通知") + if body == "" { + return robotNotifyModeNormal, "" + } + + parts := strings.Fields(body) + if len(parts) == 0 { + return robotNotifyModeNormal, "" + } + + switch parts[0] { + case robotNotifyModeNormal, robotNotifyModeAtAll, robotNotifyModeEssence: + return parts[0], strings.TrimSpace(strings.Join(parts[1:], " ")) + default: + return robotNotifyModeNormal, body + } +} + +func formatRobotGroups(title string, groups []int64) string { + if len(groups) == 0 { + return title + ":未配置" + } + + lines := make([]string, 0, len(groups)+1) + lines = append(lines, title+":") + for _, groupID := range groups { + lines = append(lines, strconv.FormatInt(groupID, 10)) + } + return strings.Join(lines, "\n") +} diff --git a/public/config/robot_blacklist.json b/public/config/robot_blacklist.json new file mode 100644 index 000000000..33accbf29 --- /dev/null +++ b/public/config/robot_blacklist.json @@ -0,0 +1,3 @@ +{ + "user_ids": [] +} diff --git a/public/config/robot_groups.json b/public/config/robot_groups.json new file mode 100644 index 000000000..47f4da53b --- /dev/null +++ b/public/config/robot_groups.json @@ -0,0 +1,4 @@ +{ + "notify_group_ids": ["274639498"], + "manage_group_ids": [] +}