修复相关火箭问题
This commit is contained in:
parent
b2362eefe8
commit
6b16f8c96e
@ -274,7 +274,7 @@
|
|||||||
| `rewards[].grant_id` | 发奖回执 |
|
| `rewards[].grant_id` | 发奖回执 |
|
||||||
| `rewards[].status` | 发奖状态 |
|
| `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`。
|
||||||
|
|
||||||
## 客户端处理
|
## 客户端处理
|
||||||
|
|
||||||
|
|||||||
@ -74,12 +74,15 @@
|
|||||||
| 贡献第一奖励 | `pending_launches[].top1_user_id` |
|
| 贡献第一奖励 | `pending_launches[].top1_user_id` |
|
||||||
| 在房奖励 | 发射命令执行时 Room Cell `OnlineUsers` |
|
| 在房奖励 | 发射命令执行时 Room Cell `OnlineUsers` |
|
||||||
|
|
||||||
`reward_stack_policy`:
|
发放规则:
|
||||||
|
|
||||||
| 值 | 说明 |
|
| 奖励 | 规则 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `allow_stack` | 同一用户可同时拿在房、top1、点火人奖励 |
|
| 在房奖励 | 每个在线用户从 `inRoomRewards` 按权重随机抽 1 个 |
|
||||||
| `priority_only` | 先点火人,再 top1,再在房;同一用户只拿一份 |
|
| 贡献第一奖励 | top1 用户获得 `top1Rewards` 配置池内全部奖励 |
|
||||||
|
| 点火人奖励 | 点火用户获得 `igniterRewards` 配置池内全部奖励 |
|
||||||
|
|
||||||
|
同一用户同时是 top1 和点火人时,top1 池和点火人池继续叠加;如果该用户发射瞬间仍在房间,也会再拿 1 个在房随机奖励。
|
||||||
|
|
||||||
## IM
|
## IM
|
||||||
|
|
||||||
@ -119,7 +122,7 @@
|
|||||||
| `launch_delay_ms` | 点火到发射延迟 |
|
| `launch_delay_ms` | 点火到发射延迟 |
|
||||||
| `broadcast_enabled` | 是否发飘屏 |
|
| `broadcast_enabled` | 是否发飘屏 |
|
||||||
| `broadcast_scope` | `region`、`global`、`none` |
|
| `broadcast_scope` | `region`、`global`、`none` |
|
||||||
| `reward_stack_policy` | 奖励叠加策略 |
|
| `reward_stack_policy` | 历史兼容字段;当前特殊身份奖励按上方规则叠加 |
|
||||||
| `levels_json` | 5 级阈值、物料和奖励池 |
|
| `levels_json` | 5 级阈值、物料和奖励池 |
|
||||||
| `gift_fuel_rules_json` | 礼物燃料规则 |
|
| `gift_fuel_rules_json` | 礼物燃料规则 |
|
||||||
|
|
||||||
|
|||||||
@ -913,21 +913,16 @@ func sortedRocketOnlineUserIDs(users map[int64]*state.RoomUserState) []int64 {
|
|||||||
return userIDs
|
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 {
|
if current == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+2)
|
rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+len(levelCfg.Top1Rewards)+len(levelCfg.IgniterRewards))
|
||||||
claimed := make(map[int64]bool, len(inRoomUserIDs)+2)
|
addRewardItem := func(role string, userID int64, item RoomRocketRewardItem) error {
|
||||||
addReward := func(role string, userID int64, pool []RoomRocketRewardItem) error {
|
|
||||||
if userID <= 0 {
|
if userID <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if cfg.RewardStackPolicy == roomRocketRewardStackPriority && claimed[userID] {
|
if item.ResourceGroupID <= 0 || item.Weight <= 0 {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
grantID, err := s.grantRoomRocketReward(ctx, now, current, launch.RocketID, role, userID, item)
|
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,
|
GrantID: grantID,
|
||||||
Status: roomRocketGrantStatus,
|
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
|
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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rewards, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, userID := range inRoomUserIDs {
|
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 nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := addReward(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
|
// top1 和点火人是确定身份权益:命中后发完整配置池;同一用户同时命中两个身份时继续叠加。
|
||||||
|
if err := addAllRewards(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
|
||||||
return nil, err
|
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 nil, err
|
||||||
}
|
}
|
||||||
return rewards, nil
|
return rewards, nil
|
||||||
|
|||||||
@ -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) {
|
func TestRoomRocketUTCResetCancelsCrossDayIgnited(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
repository := mysqltest.NewRepository(t)
|
repository := mysqltest.NewRepository(t)
|
||||||
@ -676,6 +747,16 @@ func hasRocketGrant(grants []*walletv1.GrantResourceGroupRequest, userID int64,
|
|||||||
return false
|
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 {
|
func rocketRewardUserIDs(rewards []*roomv1.RoomRocketRewardGrant) []int64 {
|
||||||
userIDs := make([]int64, 0, len(rewards))
|
userIDs := make([]int64, 0, len(rewards))
|
||||||
for _, reward := range rewards {
|
for _, reward := range rewards {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user