Compare commits

...

21 Commits

Author SHA1 Message Date
昔念
da118dc826 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-18 16:34:03 +08:00
昔念
823eef00ac 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 15:30:00 +08:00
昔念
7844c5b76b 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-18 15:22:30 +08:00
昔念
4abd179a23 Switch Woodpecker SSH plugin image to Huawei Cloud mirror
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-18 13:54:35 +08:00
昔念
a3e88c7357 Switch Woodpecker SSH step to ghcr drone-ssh image
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 13:33:18 +08:00
昔念
4e1a9a815f Switch Woodpecker SSH step to plugins/ssh image
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 13:22:44 +08:00
昔念
de3ae0bca2 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 13:09:12 +08:00
昔念
b1ff4d3a2a Switch Woodpecker Go image to DaoCloud mirror
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 12:44:59 +08:00
昔念
24b52e14c3 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 12:35:40 +08:00
昔念
2b92baf530 Use mirror image sources in Woodpecker pipeline 2026-04-18 12:24:34 +08:00
昔念
819d5f667b Set git-sync mode to push for force overwrite sync 2026-04-18 12:13:43 +08:00
昔念
de6c700bb3 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 01:30:48 +08:00
昔念
3232efd05a 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-17 00:48:43 +08:00
昔念
0c79fee8af 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-17 00:35:17 +08:00
昔念
3d77e146e9 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-16 23:49:28 +08:00
xinian
a43a25c610 test: cover legacy round broadcast handling
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-16 10:25:56 +08:00
xinian
3cfde577eb test: add pet fusion transaction coverage
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-16 09:21:39 +08:00
xinian
85f9c02ced fix: correct self-destruct mutual KO handling
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-16 09:21:02 +08:00
昔念
9f7fd83626 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 22:42:56 +08:00
昔念
ee8b0a2182 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 22:17:08 +08:00
昔念
6e95e014fa 1 2026-04-15 22:16:56 +08:00
30 changed files with 664 additions and 94 deletions

View File

@@ -27,5 +27,4 @@ main:
username: ${GIT_USERNAME} username: ${GIT_USERNAME}
password: ${GIT_ACCESS_TOKEN} password: ${GIT_ACCESS_TOKEN}
force: true force: true
sync_mode: push
#sync_mode: rebase

View File

@@ -13,7 +13,7 @@ skip_clone: true
steps: steps:
# ========== 1. 替代clone拉取代码核心依赖 ========== # ========== 1. 替代clone拉取代码核心依赖 ==========
prepare: prepare:
image: alpine/git image: docker.1ms.run/alpine/git
environment: environment:
# WOODPECKER_SSH_KEY: # WOODPECKER_SSH_KEY:
# from_secret: WOODPECKER_SSH_KEY # from_secret: WOODPECKER_SSH_KEY
@@ -70,7 +70,7 @@ steps:
# ========== 4. 编译Logic服务完全参考GitHub Actions编译配置 ========== # ========== 4. 编译Logic服务完全参考GitHub Actions编译配置 ==========
build_logic: build_logic:
image: golang:1.25 image: docker.m.daocloud.io/golang:1.25
depends_on: [prepare] depends_on: [prepare]
environment: environment:
CGO_ENABLED: 0 CGO_ENABLED: 0
@@ -142,7 +142,7 @@ steps:
# ========== 6. SCP推送产物依赖编译+配置解析 ========== # ========== 6. SCP推送产物依赖编译+配置解析 ==========
scp-exe-to-servers: # 与fetch-deploy-config同级缩进2个空格 scp-exe-to-servers: # 与fetch-deploy-config同级缩进2个空格
image: appleboy/drone-scp:1.6.2 # 子元素缩进4个空格 image: docker.1ms.run/appleboy/drone-scp:1.6.2 # 子元素缩进4个空格
settings: # 子元素缩进4个空格 settings: # 子元素缩进4个空格
host: &ssh_host 43.248.3.21 host: &ssh_host 43.248.3.21
port: &ssh_port 22 port: &ssh_port 22
@@ -158,7 +158,7 @@ steps:
depends_on: # 子元素缩进4个空格 depends_on: # 子元素缩进4个空格
- build_logic # depends_on内的项缩进6个空格 - build_logic # depends_on内的项缩进6个空格
start-login-logic: start-login-logic:
image: appleboy/drone-ssh:1.6.2 image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/appleboy/drone-ssh:1.7.7
depends_on: [scp-exe-to-servers] depends_on: [scp-exe-to-servers]
settings: # 子元素缩进4个空格 settings: # 子元素缩进4个空格
host: *ssh_host host: *ssh_host

View File

@@ -2,6 +2,7 @@ package cool
import ( import (
"context" "context"
"reflect"
"strings" "strings"
"blazing/cool/coolconfig" "blazing/cool/coolconfig"
@@ -158,12 +159,33 @@ func (c *Controller) Page(ctx context.Context, req *PageReq) (res *BaseRes, err
// 注册控制器到路由 // 注册控制器到路由
func RegisterController(c IController) { func RegisterController(c IController) {
var ctx = context.Background() var ctx = context.Background()
var sController = &Controller{} var sController *Controller
gconv.Struct(c, &sController) rv := reflect.ValueOf(c)
if rv.IsValid() && rv.Kind() == reflect.Ptr {
ev := rv.Elem()
if ev.IsValid() {
field := ev.FieldByName("Controller")
if field.IsValid() && !field.IsNil() {
if ctrl, ok := field.Interface().(*Controller); ok && ctrl != nil {
sController = ctrl
}
}
}
}
if sController == nil {
sController = &Controller{}
gconv.Struct(c, &sController)
}
if coolconfig.Config.Eps { if coolconfig.Config.Eps {
model := sController.Service.GetModel() model := sController.Service.GetModel()
columns := getModelInfo(ctx, sController.Prefix, model) tableName := ""
ModelInfo[sController.Prefix] = columns if model != nil {
tableName = strings.TrimSpace(model.TableName())
}
if tableName != "" && tableName != "this_table_should_not_exist" {
columns := getModelInfo(ctx, sController.Prefix, model)
ModelInfo[sController.Prefix] = columns
}
} }
g.Server().Group( g.Server().Group(
sController.Prefix, func(group *ghttp.RouterGroup) { sController.Prefix, func(group *ghttp.RouterGroup) {

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,73 @@
-- 初始化/修复 SPT 配置表PostgreSQL
-- 用法 sun 数据库执行本文件
BEGIN;
CREATE TABLE IF NOT EXISTS config_spt (
id BIGSERIAL PRIMARY KEY,
"createTime" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
"updateTime" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL,
is_enable INTEGER NOT NULL DEFAULT 1,
remark VARCHAR(255) NOT NULL DEFAULT '',
task_id INTEGER NOT NULL,
title VARCHAR(64) NOT NULL DEFAULT '',
pet_id INTEGER NOT NULL DEFAULT 0,
online INTEGER NOT NULL DEFAULT 1,
level INTEGER NOT NULL DEFAULT 1,
seat_id INTEGER NOT NULL DEFAULT 0,
enter_id INTEGER NOT NULL DEFAULT 0,
description TEXT NOT NULL DEFAULT ''
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_spt_task_id ON config_spt(task_id);
INSERT INTO config_spt
(task_id, title, pet_id, online, level, seat_id, enter_id, description, is_enable, remark)
VALUES
(301,'蘑菇怪',47,1,1,12,12,'生活在克洛斯星被艾里逊的液氮冻伤而发狂使用火焰喷射器可以使它安静下来制服它可以获得草系精灵小蘑菇',1,'破除防护罩'),
(302,'钢牙鲨',34,1,1,22,21,'海洋星海底的危险怪兽据说它躲藏的洞穴中有制作黑武士装的黑晶矿石记住到海底一定要穿上耐压的潜水套装',1,''),
(303,'里奥斯',42,1,2,17,17,'海盗艾里逊在火山被它困住战胜它有机会获得火系精灵胡里亚在火山你会用到喷水装的',1,'扑灭火焰屏障'),
(304,'阿克希亚',50,1,4,40,40,'塞西利亚星的守护者正义的精灵圣兽它是不可战胜的千年来一直等待着宿命的对手',1,''),
(305,'提亚斯',69,1,3,27,27,'云霄星出现了一只极具攻击性的变异精灵拥有很多蛋的它虽然想要努力呵护自己的孩子却力不从心看来需要大家帮帮忙啊',1,''),
(306,'雷伊',70,1,3,32,32,'赫尔卡星天空中划过一道闪电映出了一个酷似精灵的黑影它全身被电流包围从它的神态中可以看出它正等待来自各方的挑战',1,'雷雨天'),
(307,'纳多雷',88,1,3,106,106,'在双子阿尔法星上特派队遇见了一只巨大的精灵经过多次挑战后它仍然丝毫无损赛尔们是否有办法战胜这只精灵呢',1,''),
(308,'雷纳多',113,1,3,49,49,'彪悍的雷纳多盘踞在双子贝塔星上和双子阿尔法星的纳多雷遥相对应守护着星球上所有精灵的',1,''),
(309,'尤纳斯',132,1,4,314,314,'黑暗之门的制造者拥有能够抵御一切的暗影屏障和所有能量来源的黑暗之核',1,''),
(310,'魔狮迪露',187,1,4,53,53,'魔狮迪露具有神秘的力量能使自己的体力突破界限但同时也会受到未知的惩罚',1,''),
(311,'哈莫雷特',216,1,5,60,60,'拥有无比巨大的身躯集水火草三种原能为一身龙系的神秘力量使它所向无敌失忆的它似乎还有很多谜团',1,''),
(312,'奈尼芬多',264,1,4,325,325,'奈尼芬多是爱迪星的守护者凄美的歌声连月亮都为之倾倒据说只有音乐的力量才能够唤醒它',1,''),
(316,'厄尔塞拉',421,1,5,61,61,'浑身散发着各色光芒任何邪恶在她的光芒下消散无形',1,''),
(50,'卡特斯',169,1,2,110,110,'作为暗黑武斗场的试炼精灵守护着试炼之门它的气度和风度非同一般杀气重重很难对付',1,''),
(51,'魔牙鲨',171,1,3,503,503,'暗黑第一门的魔牙鲨被赋予了传说中的暗黑斗气隐藏在暗影中攻击时它的能力可以被放大增强',1,''),
(53,'贝鲁基德',174,1,3,504,504,'暗黑第二门的贝鲁基德暗黑火焰环绕周身凶悍的外表下藏着善战勇敢的心',1,''),
(55,'巴弗洛',177,1,3,505,505,'勇猛凶横的巴弗洛把守着暗黑武斗场-霹雳闪电般的羽翼攻击震荡心胸的音乐攻击让人防不胜防',1,''),
(56,'奇拉塔顿',183,1,3,505,505,'勇敢的奇拉塔顿驻守在暗黑武斗场-门的那一边身为大地之子的它驾驭着反物质能量纵横无敌',1,''),
(59,'西萨拉斯',195,1,3,506,506,'拥有强大反物质电力的暗黑-门守护者雷霆之刃震撼寰宇天地电流之剑穿透空间阻隔威慑四方',1,''),
(60,'克林卡修',192,1,4,506,506,'暗黑-门守护者冰雪灵兽克林卡修冰雪之爪具有猛烈的攻击力果敢不张扬的个性让它成为忍者般的精灵',1,''),
(76,'卡库',222,1,4,507,507,'暗黑-门是武学之门守门精灵不张扬不蛮横步步为营每出一招都会致命',1,''),
(77,'赫德卡',224,1,4,507,507,'驻守暗黑V-II门的铁血赫德卡有着铜墙铁壁的防守能力有着超级强力的电光炮它的防御之门你能够开启吗',1,''),
(78,'伊兰罗尼',227,1,5,507,507,'守护暗黑-门的伊兰罗尼是优雅得体的淑女擅长在裙摆飘飘光芒闪耀间使出杀手',1,''),
(117,'斯加尔卡',356,1,5,508,508,'暗黑VI-I门的守护者擅长使用暗黑电能的家伙比起折磨对手的身体斯加尔卡更喜欢震慑对手的心灵',1,''),
(118,'艾尔伊洛',297,1,5,508,508,'暗黑VI-II门的守护者历经了炼狱洗礼的艾尔伊洛开始崇尚爽快的攻击方式喜欢凭借精湛的技巧近距地伤害对手',1,''),
(119,'布林克克',359,1,5,508,508,'暗黑VI-III门的守护者拥有海妖之力的庇护体内充满着混沌的能量企图吞噬整个海洋',1,''),
(502,'魔花使者',438,1,5,509,509,'暗黑VII-I门的守护者比恩特的进化形态浑身散发着反物质世界中的黑暗气息散发出来的毒粉是它的致命武器',1,''),
(503,'莫尔加斯',441,1,5,509,509,'暗黑VII-II门的守护者莫鲁格尔的进化形态受到了反物质世界的影响浑身被黑暗所包围拥有极强的防御能力所有攻击在它面前都显得非常渺小',1,''),
(504,'萨诺拉斯',435,1,5,509,509,'暗黑VII-III门的守护者萨诺的进化形态经过岩浆洗礼的皮肤拥有独特的降温功能即使在极其炎热的环境下依然不受影响',1,''),
(606,'帕多尼',656,1,5,510,510,'暗黑-门的守护者浑身充斥着暗黑能量暗黑能量会随着它的歌神散发出来',1,''),
(607,'加洛德',659,1,5,510,510,'暗黑-门的守护暗黑能量的注入使它的脾气变得暴躁擅长与对手近身搏斗浑身的尖刺催生出的植物都是它进攻的利器',1,''),
(608,'萨多拉尼',661,1,5,510,510,'暗黑-门的守护将暗黑能量融入自身肢体变得非常结实有力虽然体积很小但是却拥有了堪比巨龙的神力',1,'')
ON CONFLICT (task_id) DO UPDATE
SET
title = EXCLUDED.title,
pet_id = EXCLUDED.pet_id,
online = EXCLUDED.online,
level = EXCLUDED.level,
seat_id = EXCLUDED.seat_id,
enter_id = EXCLUDED.enter_id,
description = EXCLUDED.description,
is_enable = EXCLUDED.is_enable,
remark = EXCLUDED.remark,
"updateTime" = NOW();
COMMIT;

View File

@@ -19,20 +19,13 @@ func (h Controller) SavePetBagOrder(
return nil, 0 return nil, 0
} }
// PetRetrieveFromWarehouse 领回仓库精灵 // PetRetrieveFromWarehouse 从放生仓库领回精灵
func (h Controller) PetRetrieveFromWarehouse( func (h Controller) PetRetrieveFromWarehouse(
data *PET_RETRIEVE, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { data *PET_RETRIEVE, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if _, ok := player.FindPetBagSlot(data.CatchTime); ok { if !player.Service.Pet.UpdateFree(data.CatchTime, 1, 0) {
return nil, 0 return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
} }
petInfo := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
if petInfo == nil {
return nil, 0
}
player.AddPetToAvailableBag(petInfo.Data)
return nil, 0 return nil, 0
} }

View File

@@ -48,9 +48,8 @@ func (h Controller) PetReleaseToWarehouse(
if inBag || inBackup || freeForbidden == 1 { if inBag || inBackup || freeForbidden == 1 {
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
} }
if !player.Service.Pet.UpdateFree(data.CatchTime, 0, 1) {
if !player.Service.Pet.UpdateFree(data.CatchTime, 1) { return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
return nil, errorcode.ErrorCodes.ErrSystemError
} }
return nil, 0 return nil, 0

View File

@@ -4,6 +4,7 @@ import (
"blazing/common/socket/errorcode" "blazing/common/socket/errorcode"
logicplayer "blazing/logic/service/player" logicplayer "blazing/logic/service/player"
"blazing/logic/service/user" "blazing/logic/service/user"
baseservice "blazing/modules/base/service"
configservice "blazing/modules/config/service" configservice "blazing/modules/config/service"
playerservice "blazing/modules/player/service" playerservice "blazing/modules/player/service"
"strings" "strings"
@@ -14,6 +15,11 @@ import (
func (h Controller) CDK(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player) (result *user.S2C_GET_GIFT_COMPLETE, err errorcode.ErrorCode) { func (h Controller) CDK(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player) (result *user.S2C_GET_GIFT_COMPLETE, err errorcode.ErrorCode) {
result = &user.S2C_GET_GIFT_COMPLETE{} result = &user.S2C_GET_GIFT_COMPLETE{}
userInfo := baseservice.NewBaseSysUserService().GetPerson(data.Head.UserID)
if userInfo == nil || userInfo.QQ == 0 {
return nil, errorcode.ErrorCodes.ErrCannotPerformAction
}
cdkCode := strings.Trim(data.PassText, "\x00") cdkCode := strings.Trim(data.PassText, "\x00")
cdkService := configservice.NewCdkService() cdkService := configservice.NewCdkService()
now := time.Now() now := time.Now()
@@ -35,7 +41,12 @@ func (h Controller) CDK(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player)
return nil, errorcode.ErrorCodes.ErrMolecularCodeGiftsGone return nil, errorcode.ErrorCodes.ErrMolecularCodeGiftsGone
} }
reward, grantErr := playerservice.NewCdkService(data.Head.UserID).GrantConfigReward(uint32(r.ID)) reward, grantErr := playerservice.NewCdkService(data.Head.UserID).GrantConfigReward(
uint32(r.ID),
func(itemID uint32, count int64) bool {
return player.ItemAdd(int64(itemID), count)
},
)
if grantErr != nil { if grantErr != nil {
return nil, errorcode.ErrorCodes.ErrSystemError return nil, errorcode.ErrorCodes.ErrSystemError
} }

View File

@@ -140,7 +140,7 @@ func (e *Effect1181) OnSkill() bool {
type Effect1182 struct{ node.EffectNode } type Effect1182 struct{ node.EffectNode }
func (e *Effect1182) Skill_Use() bool { func (e *Effect1182) Skill_Use() bool {
if len(e.Args()) < 2 || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil { if len(e.Args()) < 2 || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil {
return true return true
} }
@@ -153,9 +153,15 @@ func (e *Effect1182) Skill_Use() bool {
if targetHP.Cmp(alpacadecimal.Zero) < 0 { if targetHP.Cmp(alpacadecimal.Zero) < 0 {
targetHP = alpacadecimal.Zero targetHP = alpacadecimal.Zero
} }
if e.Ctx().Opp.CurPet[0].GetHP().Cmp(targetHP) > 0 { forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
e.Ctx().Opp.CurPet[0].Info.Hp = uint32(targetHP.IntPart()) if target == nil || target.CurrentPet() == nil {
} return true
}
if target.CurrentPet().GetHP().Cmp(targetHP) > 0 {
target.CurrentPet().Info.Hp = uint32(targetHP.IntPart())
}
return true
})
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1182, int(e.Args()[1].IntPart())) sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1182, int(e.Args()[1].IntPart()))
if sub != nil { if sub != nil {

View File

@@ -10,20 +10,23 @@ type Effect169 struct {
} }
func (e *Effect169) OnSkill() bool { func (e *Effect169) OnSkill() bool {
chance := e.Args()[1].IntPart() chance := e.Args()[1].IntPart()
success, _, _ := e.Input.Player.Roll(int(chance), 100) success, _, _ := e.Input.Player.Roll(int(chance), 100)
if success { if success {
// 添加异常状态 e.ForEachOpponentSlot(func(target *input.Input) bool {
statusEffect := e.CarrierInput().InitEffect(input.EffectType.Status, int(e.Args()[2].IntPart())) // 以麻痹为例 if target == nil {
if statusEffect != nil { return true
e.TargetInput().AddEffect(e.CarrierInput(), statusEffect) }
} statusEffect := e.CarrierInput().InitEffect(input.EffectType.Status, int(e.Args()[2].IntPart()))
if statusEffect != nil {
target.AddEffect(e.CarrierInput(), statusEffect)
}
return true
})
} }
return true return true
} }
func init() { func init() {
input.InitEffect(input.EffectType.Skill, 169, &Effect169{}) input.InitEffect(input.EffectType.Skill, 169, &Effect169{})
} }

View File

@@ -1,6 +1,7 @@
package effect package effect
import ( import (
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node" "blazing/logic/service/fight/node"
) )
@@ -41,14 +42,16 @@ type Effect5 struct {
// 技能触发时调用 // 技能触发时调用
// ----------------------------------------------------------- // -----------------------------------------------------------
func (e *Effect5) Skill_Use() bool { func (e *Effect5) Skill_Use() bool {
// 概率判定 // 概率判定
ok, _, _ := e.Input.Player.Roll(e.SideEffectArgs[1], 100) ok, _, _ := e.Input.Player.Roll(e.SideEffectArgs[1], 100)
if !ok { if !ok {
return true return true
} }
e.Ctx().Opp.SetProp(e.Ctx().Our, int8(e.SideEffectArgs[0]), int8(e.SideEffectArgs[2])) forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
target.SetProp(e.Ctx().Our, int8(e.SideEffectArgs[0]), int8(e.SideEffectArgs[2]))
return true
})
return true return true
} }

View File

@@ -22,15 +22,19 @@ type Effect76 struct {
} }
func (e *Effect76) OnSkill() bool { func (e *Effect76) OnSkill() bool {
// 概率判定 // 概率判定
ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100) ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100)
if !ok { if !ok {
return true return true
} }
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed, damage := alpacadecimal.NewFromInt(int64(e.SideEffectArgs[2]))
Damage: alpacadecimal.NewFromInt(int64(e.SideEffectArgs[2])), forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
target.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: damage,
})
return true
}) })
return true return true
} }

View File

@@ -120,7 +120,7 @@ var effectInfoByID = map[int]string{
164: "{0}回合内若受到攻击则有{1}%概率令对手{2}", 164: "{0}回合内若受到攻击则有{1}%概率令对手{2}",
165: "{0}回合内每回合防御和特防等级+{1}", 165: "{0}回合内每回合防御和特防等级+{1}",
166: "{0}回合内若对手使用属性攻击则{2}%对手{1}等级{3}", 166: "{0}回合内若对手使用属性攻击则{2}%对手{1}等级{3}",
169: "{0}回合内每回合额外附加{1}%概率令对{2}", 169: "{0}回合内每回合额外附加{1}%概率令对方阵营全体{2}",
170: "若先出手则免疫当回合伤害并回复1/{0}的最大体力值", 170: "若先出手则免疫当回合伤害并回复1/{0}的最大体力值",
171: "{0}回合内自身使用属性技能时能较快出手", 171: "{0}回合内自身使用属性技能时能较快出手",
172: "若后出手则给予对方损伤的1/{0}会回复自己的体力", 172: "若后出手则给予对方损伤的1/{0}会回复自己的体力",

View File

@@ -207,9 +207,15 @@ func registerSelfDamageOnSkillEffects() {
}) })
} }
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{ forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
Type: info.DamageType.Fixed, if target == nil || target.CurrentPet() == nil {
Damage: opponentDamage, return true
}
target.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: opponentDamage,
})
return true
}) })
return true return true
}, },
@@ -241,9 +247,15 @@ func registerSelfDamageSkillUseEffects() {
Type: info.DamageType.Fixed, Type: info.DamageType.Fixed,
Damage: damage, Damage: damage,
}) })
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{ forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
Type: info.DamageType.Fixed, if target == nil || target.CurrentPet() == nil {
Damage: damage, return true
}
target.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: damage,
})
return true
}) })
return true return true
}, },
@@ -253,9 +265,23 @@ func registerSelfDamageSkillUseEffects() {
Damage: alpacadecimal.NewFromInt(int64(e.Ctx().Our.CurPet[0].Info.MaxHp)), Damage: alpacadecimal.NewFromInt(int64(e.Ctx().Our.CurPet[0].Info.MaxHp)),
}) })
damage := int64(grand.N(250, 300)) damage := int64(grand.N(250, 300))
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{ forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
Type: info.DamageType.Fixed, if target == nil {
Damage: alpacadecimal.Min(alpacadecimal.NewFromInt(damage), e.Ctx().Opp.CurPet[0].GetHP().Sub(alpacadecimal.NewFromInt(1))), return true
}
targetPet := target.CurrentPet()
if targetPet == nil {
return true
}
remainHP := targetPet.GetHP().Sub(alpacadecimal.NewFromInt(1))
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
target.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: alpacadecimal.Min(alpacadecimal.NewFromInt(damage), remainHP),
})
return true
}) })
return true return true
}, },
@@ -280,15 +306,25 @@ func registerSelfDamageSkillUseEffects() {
randomDamage = grand.N(minDamage, maxDamage) randomDamage = grand.N(minDamage, maxDamage)
} }
remainHP := e.Ctx().Opp.CurPet[0].GetHP().Sub(alpacadecimal.NewFromInt(1)) forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
if remainHP.Cmp(alpacadecimal.Zero) <= 0 { if target == nil {
return true return true
} }
targetPet := target.CurrentPet()
if targetPet == nil {
return true
}
remainHP := targetPet.GetHP().Sub(alpacadecimal.NewFromInt(1))
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
damage := alpacadecimal.Min(alpacadecimal.NewFromInt(int64(randomDamage)), remainHP) damage := alpacadecimal.Min(alpacadecimal.NewFromInt(int64(randomDamage)), remainHP)
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{ target.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed, Type: info.DamageType.Fixed,
Damage: damage, Damage: damage,
})
return true
}) })
return true return true
}, },
@@ -297,11 +333,17 @@ func registerSelfDamageSkillUseEffects() {
return true return true
} }
applyAllPropDown(e.Ctx().Our, e.Ctx().Opp, int8(e.Args()[0].IntPart())) forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1380, int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart())) if target == nil || target.CurrentPet() == nil {
if sub != nil { return true
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub) }
} applyAllPropDown(e.Ctx().Our, target, int8(e.Args()[0].IntPart()))
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1380, int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart()))
if sub != nil {
target.AddEffect(e.Ctx().Our, sub)
}
return true
})
e.Ctx().Our.Damage(e.Ctx().Our, &info.DamageZone{ e.Ctx().Our.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed, Type: info.DamageType.Fixed,
Damage: e.Ctx().Our.CurPet[0].GetHP(), Damage: e.Ctx().Our.CurPet[0].GetHP(),

View File

@@ -0,0 +1,34 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
)
// forEachEnemyTargetBySkill 在普通情况下对单个目标生效;
// 当技能为 AtkType=3仅自己且当前目标仍在己方时改为遍历敌方全部站位。
func forEachEnemyTargetBySkill(carrier, target *input.Input, skill *info.SkillEntity, fn func(*input.Input) bool) {
if fn == nil {
return
}
if carrier == nil {
if target != nil {
fn(target)
}
return
}
if skill == nil || skill.XML.AtkType != 3 || !isSameSideTarget(carrier, target) {
if target != nil {
fn(target)
}
return
}
for _, opponent := range carrier.OpponentSlots() {
if opponent == nil {
continue
}
if !fn(opponent) {
return
}
}
}

View File

@@ -7,6 +7,8 @@ import (
"blazing/logic/service/fight/action" "blazing/logic/service/fight/action"
"blazing/logic/service/fight/info" "blazing/logic/service/fight/info"
"blazing/logic/service/fight/input" "blazing/logic/service/fight/input"
_ "blazing/logic/service/fight/itemover"
_ "blazing/logic/service/fight/rule"
"blazing/modules/player/model" "blazing/modules/player/model"
"reflect" "reflect"
@@ -133,7 +135,20 @@ func (f *FightC) getSkillParticipants(skillAction *action.SelectSkillAction) (*i
if skillAction == nil { if skillAction == nil {
return nil, nil return nil, nil
} }
return f.GetInputByAction(skillAction, false), f.GetInputByAction(skillAction, true) attacker := f.GetInputByAction(skillAction, false)
defender := f.GetInputByAction(skillAction, true)
if attacker != nil && defender == attacker && shouldResolveOpponentAsTarget(skillAction.SkillEntity) {
if opponent, _ := attacker.OpponentSlotAtOrNextLiving(0); opponent != nil {
defender = opponent
} else if opponent := f.roundOpponentInput(attacker); opponent != nil {
defender = opponent
}
}
return attacker, defender
}
func shouldResolveOpponentAsTarget(skill *info.SkillEntity) bool {
return skill != nil && skill.XML.AtkType == 3
} }
// setEffectSkillContext 统一设置技能阶段 effect 上下文。 // setEffectSkillContext 统一设置技能阶段 effect 上下文。
@@ -232,6 +247,15 @@ func (f *FightC) roundOpponentInput(attacker *input.Input) *input.Input {
return nil return nil
} }
func shouldSkipSecondAction(first, second *input.Input) bool {
if first == nil || second == nil {
return false
}
firstPet := first.CurrentPet()
secondPet := second.CurrentPet()
return firstPet == nil || firstPet.Info.Hp <= 0 || secondPet == nil || secondPet.Info.Hp <= 0
}
// enterturn 处理战斗回合逻辑 // enterturn 处理战斗回合逻辑
// 回合有先手方和后手方,同时有攻击方和被攻击方 // 回合有先手方和后手方,同时有攻击方和被攻击方
func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) { func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) {
@@ -333,6 +357,10 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
if currentAction == nil { if currentAction == nil {
continue continue
} }
if shouldSkipSecondAction(f.First, f.Second) {
secondAttack = nil
continue
}
attacker, defender = f.getSkillParticipants(secondAttack) attacker, defender = f.getSkillParticipants(secondAttack)
originalSkill = f.copySkill(secondAttack) originalSkill = f.copySkill(secondAttack)
//取消后手历史效果 //取消后手历史效果

View File

@@ -8,6 +8,11 @@ import (
"blazing/modules/player/model" "blazing/modules/player/model"
) )
// <!--
// GBTL:
// 1. AtkNum:本技能同时攻击数量, 默认:1(不能为0)
// 2. AtkType:攻击类型: 0:所有人, 1:仅己方, 2:仅对方, 3:仅自己, 默认:2
// -->
const ( const (
groupCmdReadyToFight uint32 = 7555 groupCmdReadyToFight uint32 = 7555
groupCmdReadyFightFinish uint32 = 7556 groupCmdReadyFightFinish uint32 = 7556
@@ -492,15 +497,23 @@ func (f *FightC) sendLegacyRoundBroadcast(firstAttack, secondAttack *action.Sele
if f == nil || !f.LegacyGroupProtocol { if f == nil || !f.LegacyGroupProtocol {
return return
} }
if firstAttack != nil { if f.legacySkillExecuted(firstAttack) {
f.sendLegacyGroupSkillHurt(firstAttack) f.sendLegacyGroupSkillHurt(firstAttack)
} }
if secondAttack != nil { if f.legacySkillExecuted(secondAttack) {
f.sendLegacyGroupSkillHurt(secondAttack) f.sendLegacyGroupSkillHurt(secondAttack)
} }
f.sendLegacyGroupBoutDone() f.sendLegacyGroupBoutDone()
} }
func (f *FightC) legacySkillExecuted(skillAction *action.SelectSkillAction) bool {
if f == nil || skillAction == nil {
return false
}
attacker := f.GetInputByAction(skillAction, false)
return attacker != nil && attacker.AttackValue != nil && attacker.AttackValue.SkillID != 0
}
func (f *FightC) sendLegacyGroupSkillHurt(skillAction *action.SelectSkillAction) { func (f *FightC) sendLegacyGroupSkillHurt(skillAction *action.SelectSkillAction) {
if f == nil || !f.LegacyGroupProtocol || skillAction == nil { if f == nil || !f.LegacyGroupProtocol || skillAction == nil {
return return
@@ -580,10 +593,10 @@ func (f *FightC) buildLegacyGroupSkillAttackInfo(skillAction *action.SelectSkill
if attackValue == nil { if attackValue == nil {
attackValue = info.NewAttackValue(self.UserID) attackValue = info.NewAttackValue(self.UserID)
} }
if skillAction != nil && skillAction.SkillEntity != nil { if attackValue.SkillID != 0 {
result.MoveID = uint32(skillAction.SkillEntity.XML.ID)
} else {
result.MoveID = attackValue.SkillID result.MoveID = attackValue.SkillID
} else if skillAction != nil && skillAction.SkillEntity != nil {
result.MoveID = uint32(skillAction.SkillEntity.XML.ID)
} }
result.IsCrit = attackValue.IsCritical result.IsCrit = attackValue.IsCritical
result.EffectName = attackValue.State result.EffectName = attackValue.State

View File

@@ -0,0 +1,61 @@
package fight
import (
"testing"
"blazing/logic/service/fight/action"
fightinfo "blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/player/model"
)
func TestSendLegacyRoundBroadcastSkipsUnexecutedAction(t *testing.T) {
ourPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 1001}}
oppPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 2002}}
our := input.NewInput(nil, ourPlayer)
our.InitAttackValue()
our.AttackValue.SkillID = 111
our.SetCurPetAt(0, fightinfo.CreateBattlePetEntity(model.PetInfo{
ID: 11,
Hp: 80,
MaxHp: 100,
CatchTime: 101,
}))
opp := input.NewInput(nil, oppPlayer)
opp.InitAttackValue()
opp.SetCurPetAt(0, fightinfo.CreateBattlePetEntity(model.PetInfo{
ID: 22,
Hp: 0,
MaxHp: 100,
CatchTime: 202,
}))
fc := &FightC{
Our: []*input.Input{our},
Opp: []*input.Input{opp},
LegacyGroupProtocol: true,
}
firstAttack := &action.SelectSkillAction{
BaseAction: action.BaseAction{PlayerID: ourPlayer.info.UserID, ActorIndex: 0, TargetIndex: 0},
}
secondAttack := &action.SelectSkillAction{
BaseAction: action.BaseAction{PlayerID: oppPlayer.info.UserID, ActorIndex: 0, TargetIndex: 0},
}
fc.sendLegacyRoundBroadcast(firstAttack, secondAttack)
for _, player := range []*stubPlayer{ourPlayer, oppPlayer} {
if len(player.sentCmds) != 2 {
t.Fatalf("expected one skill packet plus bout done, got %v", player.sentCmds)
}
if player.sentCmds[0] != groupCmdSkillHurt {
t.Fatalf("expected first packet to be skill hurt, got %d", player.sentCmds[0])
}
if player.sentCmds[1] != groupCmdBoutDone {
t.Fatalf("expected second packet to be bout done, got %d", player.sentCmds[1])
}
}
}

View File

@@ -586,6 +586,7 @@ func (f *FightC) handleItemAction(a *action.UseItemAction) {
if r <= 0 { if r <= 0 {
return return
} }
source.Player.(*player.Player).Service.Item.UPDATE(a.ItemID, -1) source.Player.(*player.Player).Service.Item.UPDATE(a.ItemID, -1)
switch { switch {

View File

@@ -12,7 +12,8 @@ import (
) )
type stubPlayer struct { type stubPlayer struct {
info model.PlayerInfo info model.PlayerInfo
sentCmds []uint32
} }
func (*stubPlayer) ApplyPetDisplayInfo(*spaceinfo.SimpleInfo) {} func (*stubPlayer) ApplyPetDisplayInfo(*spaceinfo.SimpleInfo) {}
@@ -26,7 +27,7 @@ func (*stubPlayer) SetFightC(common.FightI) {}
func (*stubPlayer) QuitFight() {} func (*stubPlayer) QuitFight() {}
func (*stubPlayer) MessWin(bool) {} func (*stubPlayer) MessWin(bool) {}
func (*stubPlayer) CanFight() errorcode.ErrorCode { return 0 } func (*stubPlayer) CanFight() errorcode.ErrorCode { return 0 }
func (*stubPlayer) SendPackCmd(uint32, any) {} func (p *stubPlayer) SendPackCmd(cmd uint32, _ any) { p.sentCmds = append(p.sentCmds, cmd) }
func (*stubPlayer) GetPetInfo(uint32) []model.PetInfo { return nil } func (*stubPlayer) GetPetInfo(uint32) []model.PetInfo { return nil }
func TestFightActionEnvelopeEncodedTargetIndex(t *testing.T) { func TestFightActionEnvelopeEncodedTargetIndex(t *testing.T) {

View File

@@ -74,7 +74,6 @@ var limiter *ratelimit.Rule = ratelimit.NewRule()
// 简单规则案例 // 简单规则案例
func init() { func init() {
//步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则 //步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则
limiter.AddRule(time.Second*1, 20) limiter.AddRule(time.Second*1, 20)
//步骤三:调用函数判断某用户是否允许访问 allow:= r.AllowVisit(user) //步骤三:调用函数判断某用户是否允许访问 allow:= r.AllowVisit(user)

View File

@@ -2,6 +2,64 @@ package blazing
import ( import (
_ "blazing/modules/config/controller" _ "blazing/modules/config/controller"
_ "blazing/modules/config/model"
_ "blazing/modules/config/service" _ "blazing/modules/config/service"
"blazing/cool"
configModel "blazing/modules/config/model"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gctx"
) )
func init() {
var (
ctx = gctx.GetInitCtx()
)
cool.Logger.Debug(ctx, "module config init start ...")
// 首次初始化 SPT 默认数据(不依赖 XML
sptModel := configModel.NewSptConfig()
count, err := g.DB("default").Model(sptModel.TableName()).Count()
if err != nil {
cool.Logger.Warning(ctx, "count config_spt failed:", err)
} else if count == 0 {
initPath := "modules/config/resource/initjson/config_spt.json"
content := gfile.GetBytes(initPath)
if len(content) == 0 {
cool.Logger.Warning(ctx, "config_spt init file is empty:", initPath)
} else {
jsonData, jErr := gjson.LoadContent(content)
if jErr != nil {
cool.Logger.Warning(ctx, "load config_spt init json failed:", jErr)
} else {
_, err = g.DB("default").Model(sptModel.TableName()).Data(jsonData.Var()).Insert()
}
}
if err != nil {
cool.Logger.Warning(ctx, "insert default config_spt failed:", err)
}
}
menuCount, err := g.DB("default").Model("base_sys_menu").Where("router", "/config/spt").Count()
if err != nil {
cool.Logger.Warning(ctx, "count SPT menu failed:", err)
} else if menuCount == 0 {
_, err = g.DB("default").Model("base_sys_menu").Data(g.Map{
"parentId": 2,
"name": "SPT配置",
"router": "/config/spt",
"viewPath": "config/views/spt.vue",
"icon": "icon-menu",
"ordernum": 70,
"keepAlive": true,
"isShow": true,
"type": 1,
}).Insert()
if err != nil {
cool.Logger.Warning(ctx, "insert SPT menu failed:", err)
}
}
cool.Logger.Debug(ctx, "module config init finished ...")
}

View File

@@ -0,0 +1,30 @@
package admin
import (
"blazing/cool"
"blazing/modules/config/model"
"blazing/modules/config/service"
)
type SptController struct {
*cool.Controller
}
func init() {
// 仅为新加的 SPT 表做定点迁移,避免首次启用 EPS 时读取表结构报错。
db, err := cool.InitDB("default")
if err != nil {
panic(err)
}
if err = db.AutoMigrate(model.NewSptConfig()); err != nil {
panic(err)
}
cool.RegisterController(&SptController{
&cool.Controller{
Prefix: "/admin/config/spt",
Api: []string{"Add", "Delete", "Update", "Info", "List", "Page"},
Service: service.NewSptService(),
},
})
}

View File

@@ -0,0 +1,39 @@
package model
import "blazing/cool"
const (
TableNameSptConfig = "config_spt"
)
// SptConfig SPT展示配置仅保留前端展示必需字段
type SptConfig struct {
*BaseConfig
TaskID uint32 `gorm:"not null;uniqueIndex;comment:'SPT任务ID'" json:"task_id" description:"SPT任务ID"`
Title string `gorm:"type:varchar(64);not null;default:'';comment:'SPT名字'" json:"title" description:"SPT名字"`
PetID uint32 `gorm:"not null;default:0;comment:'SPT精灵ID'" json:"pet_id" description:"SPT精灵ID"`
Online int32 `gorm:"not null;default:1;comment:'是否开放(1开放/0未开放)'" json:"online" description:"是否开放"`
Level uint32 `gorm:"not null;default:1;comment:'SPT星级'" json:"level" description:"SPT星级"`
SeatID uint32 `gorm:"not null;default:0;comment:'所在地图点位ID'" json:"seat_id" description:"所在地图点位ID"`
EnterID uint32 `gorm:"not null;default:0;comment:'进入地图ID'" json:"enter_id" description:"进入地图ID"`
Description string `gorm:"type:text;not null;default:'';comment:'SPT描述'" json:"description" description:"SPT描述"`
}
func (*SptConfig) TableName() string {
return TableNameSptConfig
}
func (*SptConfig) GroupName() string {
return "default"
}
func NewSptConfig() *SptConfig {
return &SptConfig{
BaseConfig: NewBaseConfig(),
}
}
func init() {
cool.CreateTable(&SptConfig{})
}

View File

@@ -0,0 +1,31 @@
package service
import (
"blazing/cool"
"blazing/modules/config/model"
)
type SptService struct {
*cool.Service
}
func NewSptService() *SptService {
return &SptService{
&cool.Service{
Model: model.NewSptConfig(),
ListQueryOp: &cool.QueryOp{
FieldEQ: []string{"is_enable"},
AddOrderby: map[string]string{
"task_id": "asc",
},
},
PageQueryOp: &cool.QueryOp{
FieldEQ: []string{"is_enable", "task_id"},
KeyWordField: []string{"title", "description", "remark"},
AddOrderby: map[string]string{
"task_id": "asc",
},
},
},
}
}

View File

@@ -2,6 +2,7 @@ package app
import ( import (
"blazing/cool" "blazing/cool"
baseservice "blazing/modules/base/service"
configservice "blazing/modules/config/service" configservice "blazing/modules/config/service"
playerservice "blazing/modules/player/service" playerservice "blazing/modules/player/service"
"context" "context"
@@ -118,6 +119,10 @@ func (c *CdkController) DonationRedeem(ctx context.Context, req *DonationRedeemR
return cool.Fail("未登录或登录已失效"), nil return cool.Fail("未登录或登录已失效"), nil
} }
ownerID := uint32(admin.UserId) ownerID := uint32(admin.UserId)
user := baseservice.NewBaseSysUserService().GetPerson(ownerID)
if user == nil || user.QQ == 0 {
return cool.Fail("请先绑定QQ"), nil
}
cdkCode := strings.TrimSpace(req.CDKCode) cdkCode := strings.TrimSpace(req.CDKCode)
if cdkCode == "" { if cdkCode == "" {

View File

@@ -26,8 +26,11 @@ type CdkRewardResult struct {
EVPool int64 `json:"ev_pool,omitempty"` EVPool int64 `json:"ev_pool,omitempty"`
} }
type CdkSpecialRewardAdder func(itemID uint32, count int64) bool
// GrantConfigReward 按 cdk 配置 ID 发放奖励,不处理兑换码次数和领取资格校验。 // GrantConfigReward 按 cdk 配置 ID 发放奖励,不处理兑换码次数和领取资格校验。
func (s *CdkService) GrantConfigReward(cdkID uint32) (*CdkRewardResult, error) { // 当传入 specialAdder 时,赛尔豆/累计经验/金豆/学习力会优先走在线玩家加成逻辑。
func (s *CdkService) GrantConfigReward(cdkID uint32, specialAdders ...CdkSpecialRewardAdder) (*CdkRewardResult, error) {
cfg := configservice.NewCdkService().GetByID(cdkID) cfg := configservice.NewCdkService().GetByID(cdkID)
if cfg == nil { if cfg == nil {
return nil, fmt.Errorf("绑定的CDK不存在") return nil, fmt.Errorf("绑定的CDK不存在")
@@ -45,42 +48,72 @@ func (s *CdkService) GrantConfigReward(cdkID uint32) (*CdkRewardResult, error) {
if playerInfo == nil { if playerInfo == nil {
return nil, fmt.Errorf("玩家角色不存在") return nil, fmt.Errorf("玩家角色不存在")
} }
var specialAdder CdkSpecialRewardAdder
if len(specialAdders) > 0 {
specialAdder = specialAdders[0]
}
var ( var (
infoDirty bool infoDirty bool
bagItems []data.ItemInfo bagItems []data.ItemInfo
) )
appendRewardItem := func(itemID uint32, count int64) { appendRewardItem := func(itemID uint32, count int64) error {
if itemID == 0 || count <= 0 { if itemID == 0 || count <= 0 {
return return nil
} }
switch itemID { switch itemID {
case 1: case 1:
result.Coins += count result.Coins += count
playerInfo.Coins += count if specialAdder != nil {
infoDirty = true if !specialAdder(itemID, count) {
return fmt.Errorf("在线发放赛尔豆失败")
}
} else {
playerInfo.Coins += count
infoDirty = true
}
case 3: case 3:
result.ExpPool += count result.ExpPool += count
playerInfo.ExpPool += count if specialAdder != nil {
infoDirty = true if !specialAdder(itemID, count) {
return fmt.Errorf("在线发放经验池失败")
}
} else {
playerInfo.ExpPool += count
infoDirty = true
}
case 5: case 5:
result.Gold += count result.Gold += count
if specialAdder != nil {
if !specialAdder(itemID, count) {
return fmt.Errorf("在线发放金豆失败")
}
}
case 9: case 9:
result.EVPool += count result.EVPool += count
playerInfo.EVPool += count if specialAdder != nil {
infoDirty = true if !specialAdder(itemID, count) {
return fmt.Errorf("在线发放学习力失败")
}
} else {
playerInfo.EVPool += count
infoDirty = true
}
default: default:
bagItems = append(bagItems, data.ItemInfo{ItemId: int64(itemID), ItemCnt: count}) bagItems = append(bagItems, data.ItemInfo{ItemId: int64(itemID), ItemCnt: count})
} }
return nil
} }
for _, rewardID := range cfg.ItemRewardIds { for _, rewardID := range cfg.ItemRewardIds {
itemInfo := configservice.NewItemService().GetItemCount(rewardID) itemInfo := configservice.NewItemService().GetItemCount(rewardID)
appendRewardItem(uint32(itemInfo.ItemId), itemInfo.ItemCnt) if err := appendRewardItem(uint32(itemInfo.ItemId), itemInfo.ItemCnt); err != nil {
return nil, err
}
} }
if result.Gold != 0 { if result.Gold != 0 && specialAdder == nil {
baseservice.NewBaseSysUserService().UpdateGold(s.userid, result.Gold*100) baseservice.NewBaseSysUserService().UpdateGold(s.userid, result.Gold*100)
} }
if result.FreeGold != 0 { if result.FreeGold != 0 {

View File

@@ -76,8 +76,12 @@ func (s *PetService) PetCount(flag int) int {
return ret return ret
} }
func (s *PetService) UpdateFree(catchTime, free uint32) bool { func (s *PetService) UpdateFree(catchTime, fromFree, toFree uint32) bool {
res, err := s.dbm(s.Model).Where("catch_time", catchTime).Data("free", free).Update() res, err := s.dbm(s.Model).
Where("catch_time", catchTime).
Where("free", fromFree).
Data("free", toFree).
Update()
if err != nil { if err != nil {
return false return false
} }

View File

@@ -72,6 +72,9 @@ func (s *UserService) PetFusionTx(
return nil return nil
} }
if err := syncPetSnapshotBeforeDeleteTx(tx, userID, currentInfo, masterCatchTime); err != nil {
return err
}
if err := deletePetTx(tx, userID, masterCatchTime); err != nil { if err := deletePetTx(tx, userID, masterCatchTime); err != nil {
return err return err
} }
@@ -82,6 +85,9 @@ func (s *UserService) PetFusionTx(
} }
result.CostItemUsed = used result.CostItemUsed = used
if !used { if !used {
if err := syncPetSnapshotBeforeDeleteTx(tx, userID, currentInfo, auxCatchTime); err != nil {
return err
}
if err := deletePetTx(tx, userID, auxCatchTime); err != nil { if err := deletePetTx(tx, userID, auxCatchTime); err != nil {
return err return err
} }
@@ -223,6 +229,28 @@ func updatePetDataTx(tx gdb.TX, userID uint32, catchTime uint32, pet model.PetIn
return nil return nil
} }
func syncPetSnapshotBeforeDeleteTx(tx gdb.TX, userID uint32, info model.PlayerInfo, catchTime uint32) error {
pet, ok := findPetDataSnapshotInPlayerInfo(info, catchTime)
if !ok {
return nil
}
return updatePetDataTx(tx, userID, catchTime, pet)
}
func findPetDataSnapshotInPlayerInfo(info model.PlayerInfo, catchTime uint32) (model.PetInfo, bool) {
for i := range info.PetList {
if info.PetList[i].CatchTime == catchTime {
return info.PetList[i], true
}
}
for i := range info.BackupPetList {
if info.BackupPetList[i].CatchTime == catchTime {
return info.BackupPetList[i], true
}
}
return model.PetInfo{}, false
}
func deletePetTx(tx gdb.TX, userID uint32, catchTime uint32) error { func deletePetTx(tx gdb.TX, userID uint32, catchTime uint32) error {
res, err := tx.Model(model.NewPet()). res, err := tx.Model(model.NewPet()).
Where("player_id", userID). Where("player_id", userID).

View File

@@ -0,0 +1,50 @@
package service
import (
"blazing/modules/player/model"
"testing"
)
func TestFindPetDataSnapshotInPlayerInfo(t *testing.T) {
t.Run("pet list", func(t *testing.T) {
want := model.PetInfo{CatchTime: 1001, Level: 55}
info := model.PlayerInfo{
PetList: []model.PetInfo{want},
}
got, ok := findPetDataSnapshotInPlayerInfo(info, want.CatchTime)
if !ok {
t.Fatal("expected pet snapshot in pet list")
}
if got.CatchTime != want.CatchTime || got.Level != want.Level {
t.Fatalf("unexpected pet snapshot: %+v", got)
}
})
t.Run("backup pet list", func(t *testing.T) {
want := model.PetInfo{CatchTime: 2002, Level: 66}
info := model.PlayerInfo{
BackupPetList: []model.PetInfo{want},
}
got, ok := findPetDataSnapshotInPlayerInfo(info, want.CatchTime)
if !ok {
t.Fatal("expected pet snapshot in backup pet list")
}
if got.CatchTime != want.CatchTime || got.Level != want.Level {
t.Fatalf("unexpected pet snapshot: %+v", got)
}
})
t.Run("not found", func(t *testing.T) {
info := model.PlayerInfo{
PetList: []model.PetInfo{{CatchTime: 3003}},
BackupPetList: []model.PetInfo{{CatchTime: 4004}},
}
_, ok := findPetDataSnapshotInPlayerInfo(info, 9999)
if ok {
t.Fatal("expected missing pet snapshot")
}
})
}