修复相关火箭问题

This commit is contained in:
zhx 2026-06-09 19:16:05 +08:00
parent b2362eefe8
commit 6b16f8c96e
4 changed files with 114 additions and 35 deletions

View File

@ -274,7 +274,7 @@
| `rewards[].grant_id` | 发奖回执 |
| `rewards[].status` | 发奖状态 |
奖励 IM 用于通知本次哪些人中奖。需要渲染金币数量、奖励天数、聊天气泡或麦位特效素材时,收到 IM 后调用 `GET /api/v1/rooms/{room_id}/rocket`,读取 `state.last_rewards[].resource_group`
奖励 IM 用于通知本次哪些人中奖。top1 和点火人命中后会发完整配置池,同一用户可能同时收到多条 `top1``igniter` 和 1 条在房随机奖励。需要渲染金币数量、奖励天数、聊天气泡或麦位特效素材时,收到 IM 后调用 `GET /api/v1/rooms/{room_id}/rocket`,读取 `state.last_rewards[].resource_group`
## 客户端处理

View File

@ -74,12 +74,15 @@
| 贡献第一奖励 | `pending_launches[].top1_user_id` |
| 在房奖励 | 发射命令执行时 Room Cell `OnlineUsers` |
`reward_stack_policy`
发放规则
| 值 | 说明 |
| 奖励 | 规则 |
| --- | --- |
| `allow_stack` | 同一用户可同时拿在房、top1、点火人奖励 |
| `priority_only` | 先点火人,再 top1再在房同一用户只拿一份 |
| 在房奖励 | 每个在线用户从 `inRoomRewards` 按权重随机抽 1 个 |
| 贡献第一奖励 | top1 用户获得 `top1Rewards` 配置池内全部奖励 |
| 点火人奖励 | 点火用户获得 `igniterRewards` 配置池内全部奖励 |
同一用户同时是 top1 和点火人时top1 池和点火人池继续叠加;如果该用户发射瞬间仍在房间,也会再拿 1 个在房随机奖励。
## IM
@ -119,7 +122,7 @@
| `launch_delay_ms` | 点火到发射延迟 |
| `broadcast_enabled` | 是否发飘屏 |
| `broadcast_scope` | `region``global``none` |
| `reward_stack_policy` | 奖励叠加策略 |
| `reward_stack_policy` | 历史兼容字段;当前特殊身份奖励按上方规则叠加 |
| `levels_json` | 5 级阈值、物料和奖励池 |
| `gift_fuel_rules_json` | 礼物燃料规则 |

View File

@ -913,21 +913,16 @@ func sortedRocketOnlineUserIDs(users map[int64]*state.RoomUserState) []int64 {
return userIDs
}
func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, current *state.RoomState, launch state.RocketPendingLaunch, cfg RoomRocketConfig, levelCfg RoomRocketLevelConfig, inRoomUserIDs []int64) ([]state.RocketRewardGrant, error) {
func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, current *state.RoomState, launch state.RocketPendingLaunch, _ RoomRocketConfig, levelCfg RoomRocketLevelConfig, inRoomUserIDs []int64) ([]state.RocketRewardGrant, error) {
if current == nil {
return nil, nil
}
rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+2)
claimed := make(map[int64]bool, len(inRoomUserIDs)+2)
addReward := func(role string, userID int64, pool []RoomRocketRewardItem) error {
rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+len(levelCfg.Top1Rewards)+len(levelCfg.IgniterRewards))
addRewardItem := func(role string, userID int64, item RoomRocketRewardItem) error {
if userID <= 0 {
return nil
}
if cfg.RewardStackPolicy == roomRocketRewardStackPriority && claimed[userID] {
return nil
}
item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool)
if !ok {
if item.ResourceGroupID <= 0 || item.Weight <= 0 {
return nil
}
grantID, err := s.grantRoomRocketReward(ctx, now, current, launch.RocketID, role, userID, item)
@ -944,35 +939,35 @@ func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, cu
GrantID: grantID,
Status: roomRocketGrantStatus,
})
claimed[userID] = true
return nil
}
addRandomReward := func(role string, userID int64, pool []RoomRocketRewardItem) error {
item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool)
if !ok {
return nil
}
return addRewardItem(role, userID, item)
}
addAllRewards := func(role string, userID int64, pool []RoomRocketRewardItem) error {
for _, item := range pool {
if err := addRewardItem(role, userID, item); err != nil {
return err
}
}
return nil
}
if cfg.RewardStackPolicy == roomRocketRewardStackPriority {
// 去重策略下先发特殊身份奖励,再发普通在房奖励,避免 top1/点火人被普通池占位。
if err := addReward(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil {
return nil, err
}
if err := addReward(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
return nil, err
}
for _, userID := range inRoomUserIDs {
if err := addReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil {
// 在房奖励是大面积发放池,每个在线用户只按权重抽一个奖项,避免房间人数放大单次火箭成本。
if err := addRandomReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil {
return nil, err
}
}
return rewards, nil
}
for _, userID := range inRoomUserIDs {
if err := addReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil {
// top1 和点火人是确定身份权益:命中后发完整配置池;同一用户同时命中两个身份时继续叠加。
if err := addAllRewards(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
return nil, err
}
}
if err := addReward(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
return nil, err
}
if err := addReward(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil {
if err := addAllRewards(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil {
return nil, err
}
return rewards, nil

View File

@ -433,6 +433,77 @@ func TestRoomRocketLaunchGrantsLockedTop1AndIgniterAfterLeave(t *testing.T) {
}
}
func TestRoomRocketLaunchStacksAllSpecialPoolsAndRandomInRoomReward(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
now := &fixedRoomRocketClock{now: time.Date(2026, 5, 20, 13, 0, 0, 0, time.UTC)}
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{
{BillingReceiptId: "receipt-stack", GiftPointAdded: 100, HeatValue: 100, GiftTypeCode: "normal"},
}}
svc := newRocketTestService(t, repository, wallet, now)
config := rocketConfigForTest(100, 1_000)
config.Levels[0].InRoomRewards = []roomservice.RoomRocketRewardItem{
{RewardItemID: "level-1-in-room-a", ResourceGroupID: 1001, Weight: 1, DisplayName: "In Room A"},
{RewardItemID: "level-1-in-room-b", ResourceGroupID: 1101, Weight: 1, DisplayName: "In Room B"},
}
config.Levels[0].Top1Rewards = []roomservice.RoomRocketRewardItem{
{RewardItemID: "level-1-top1-a", ResourceGroupID: 2001, Weight: 1, DisplayName: "Top 1 A"},
{RewardItemID: "level-1-top1-b", ResourceGroupID: 2101, Weight: 1, DisplayName: "Top 1 B"},
}
config.Levels[0].IgniterRewards = []roomservice.RoomRocketRewardItem{
{RewardItemID: "level-1-igniter-a", ResourceGroupID: 3001, Weight: 1, DisplayName: "Igniter A"},
{RewardItemID: "level-1-igniter-b", ResourceGroupID: 3101, Weight: 1, DisplayName: "Igniter B"},
}
writeRocketConfig(t, ctx, repository, config)
roomID := "room-rocket-stack"
ownerID := int64(401)
igniterID := int64(402)
createRocketRoom(t, ctx, svc, roomID, ownerID, 9003)
joinRocketRoom(t, ctx, svc, roomID, igniterID)
if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
Meta: rocketMeta(roomID, igniterID, "stack"),
TargetType: "user",
TargetUserId: ownerID,
GiftId: "gift-stack",
GiftCount: 1,
}); err != nil {
t.Fatalf("ignite stack rocket failed: %v", err)
}
now.now = now.now.Add(1100 * time.Millisecond)
if err := svc.SweepRoomRocketLaunchings(ctx); err != nil {
t.Fatalf("sweep stacked room rocket launches failed: %v", err)
}
info, err := svc.GetRoomRocket(ctx, &roomv1.GetRoomRocketRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
RoomId: roomID,
ViewerUserId: ownerID,
})
if err != nil {
t.Fatalf("get stacked rocket after launch failed: %v", err)
}
rewards := info.GetRocket().GetState().GetLastRewards()
if len(rewards) != 6 {
t.Fatalf("launch must keep two in-room random grants plus all top1/igniter grants: %+v", rewards)
}
if countRocketRewards(rewards, ownerID, "in_room") != 1 {
t.Fatalf("owner must receive exactly one random in-room reward: %+v", rewards)
}
if countRocketRewards(rewards, igniterID, "in_room") != 1 {
t.Fatalf("top1 igniter must still receive exactly one random in-room reward: %+v", rewards)
}
if countRocketRewards(rewards, igniterID, "top1") != 2 || countRocketRewards(rewards, igniterID, "igniter") != 2 {
t.Fatalf("top1 igniter must receive every configured special reward: %+v", rewards)
}
if !hasRocketGrant(wallet.grants, igniterID, 2001) || !hasRocketGrant(wallet.grants, igniterID, 2101) ||
!hasRocketGrant(wallet.grants, igniterID, 3001) || !hasRocketGrant(wallet.grants, igniterID, 3101) {
t.Fatalf("wallet must grant every top1 and igniter resource group: %+v", wallet.grants)
}
}
func TestRoomRocketUTCResetCancelsCrossDayIgnited(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
@ -676,6 +747,16 @@ func hasRocketGrant(grants []*walletv1.GrantResourceGroupRequest, userID int64,
return false
}
func countRocketRewards(rewards []*roomv1.RoomRocketRewardGrant, userID int64, role string) int {
count := 0
for _, reward := range rewards {
if reward.GetUserId() == userID && reward.GetRewardRole() == role {
count++
}
}
return count
}
func rocketRewardUserIDs(rewards []*roomv1.RoomRocketRewardGrant) []int64 {
userIDs := make([]int64, 0, len(rewards))
for _, reward := range rewards {