mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
604 lines
21 KiB
C#
604 lines
21 KiB
C#
using System.Collections.Concurrent;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Storefront;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Store;
|
|
using RadicadeServer.RecNet.Moderation;
|
|
using RadicadeServer.RecNet.Progression;
|
|
using RadicadeServer.RecNet.Storefront;
|
|
using RadicadeServer.Utils;
|
|
|
|
namespace RadicadeServer.API.GameRewards.V1
|
|
{
|
|
public class GameRewardRequestDTO
|
|
{
|
|
public RewardType RewardType { get; set; }
|
|
public string Message { get; set; } = string.Empty;
|
|
public GiftContext? GiftContext { get; set; }
|
|
}
|
|
|
|
public class GameRewardSelectDTO
|
|
{
|
|
public long RewardSelectionId { get; set; }
|
|
public long GiftDropId { get; set; }
|
|
}
|
|
|
|
[Route("api/gamerewards/v1")]
|
|
[Route("econ/api/gamerewards/v1")]
|
|
public class GameRewardsAPI(RecDBContext db) : Controller
|
|
{
|
|
[HttpGet("pending")]
|
|
public async Task<List<GameReward>> GetPendingGameRewards()
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null) return new List<GameReward>();
|
|
|
|
var data = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == userId.Value);
|
|
var progression = data?.GetProgressionJson();
|
|
var pending = progression?.PendingGameRewards;
|
|
if (pending == null) return new List<GameReward>();
|
|
|
|
var changed = false;
|
|
|
|
var forgotten = pending.RemoveAll(r => r.Consumed && r.ConsumedAt is { } at && at < DateTime.UtcNow.AddDays(-1));
|
|
changed |= forgotten > 0;
|
|
|
|
var outstanding = pending.Where(r => !r.Consumed).ToList();
|
|
foreach (var reward in outstanding)
|
|
changed |= ClampRewardIds(reward);
|
|
if (changed && data != null)
|
|
{
|
|
data.Progress = progression!.ToJson();
|
|
await db.SaveChangesAsync();
|
|
}
|
|
return outstanding;
|
|
}
|
|
|
|
private static bool ClampRewardIds(GameReward reward)
|
|
{
|
|
var changed = false;
|
|
|
|
void Clamp(Gift? gift)
|
|
{
|
|
if (gift == null || (gift.GiftDropId >= int.MinValue && gift.GiftDropId <= int.MaxValue)) return;
|
|
gift.GiftDropId = StorefrontShared.NewGiftDropId();
|
|
changed = true;
|
|
}
|
|
|
|
if (reward.RewardSelectionId > int.MaxValue || reward.RewardSelectionId < int.MinValue)
|
|
{
|
|
_ = reward.RewardSelectionId;
|
|
}
|
|
Clamp(reward.GiftDrop1);
|
|
Clamp(reward.GiftDrop2);
|
|
Clamp(reward.GiftDrop3);
|
|
return changed;
|
|
}
|
|
|
|
[HttpPost("request"), Authorize]
|
|
public async Task<ActionResult<OkResponse>> RequestReward()
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var dto = await ReadRewardBodyAsync();
|
|
if (dto == null) return BadRequest();
|
|
|
|
if (dto.RewardType == RewardType.WeeklyCompleteButHaveSkin)
|
|
return Ok(new OkResponse { Success = true });
|
|
|
|
Trace(userId.Value, $"claimed rewardType={dto.RewardType} ({(int)dto.RewardType}) giftContext={dto.GiftContext?.ToString() ?? "none"} body={_rawBody}");
|
|
|
|
var data = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == userId.Value);
|
|
if (data == null)
|
|
{
|
|
data = new ProgressionData { PlayerId = userId.Value };
|
|
db.Progression.Add(data);
|
|
}
|
|
var json = data.GetProgressionJson() ?? new ProgressionJson();
|
|
json.PendingGameRewards ??= new List<GameReward>();
|
|
|
|
var ledger = RewardDay.For(json, DateTime.UtcNow);
|
|
|
|
var completion = IsCompletion(dto.RewardType);
|
|
GameReward? reward = null;
|
|
|
|
if (completion)
|
|
{
|
|
var rewardType = RewardDay.CompletionType(ledger, dto.RewardType);
|
|
|
|
var paid = rewardType == RewardType.FirstActivityOfDay
|
|
? RewardDay.TryTakeFirstActivity(ledger)
|
|
: RewardDay.TryTakeActivityBox(ledger);
|
|
|
|
if (paid)
|
|
{
|
|
dto.Message = CompletionMessage(rewardType, dto.Message);
|
|
reward = await BuildRewardAsync(userId.Value, dto, rewardType);
|
|
}
|
|
}
|
|
else if (dto.RewardType == RewardType.DailyChallengesComplete)
|
|
{
|
|
if (RewardDay.TryTakeDailyChallengeBox(ledger))
|
|
{
|
|
dto.Message = string.IsNullOrWhiteSpace(dto.Message) ? "Daily Challenge Reward" : dto.Message;
|
|
reward = await BuildDailyChallengeRewardAsync(userId.Value, dto);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
reward = BuildReward(dto.RewardType, dto.Message,
|
|
BonusContext(dto.RewardType) ?? dto.GiftContext ?? GiftContext.GameRewards_Tokens);
|
|
}
|
|
|
|
if (reward != null)
|
|
json.PendingGameRewards.Add(reward);
|
|
|
|
data.Progress = json.ToJson();
|
|
await db.SaveChangesAsync();
|
|
|
|
if (completion)
|
|
{
|
|
await ProgressionAwardService.AwardXpAsync(db, userId.Value,
|
|
ProgressionAwardService.XpPerGameCompleted, "game completed");
|
|
}
|
|
|
|
if (reward == null) return Ok(new OkResponse { Success = true });
|
|
|
|
if (RroRewardPools.IsQuestContext(reward.GiftContext))
|
|
{
|
|
await DeliverQuestRewardAsync(userId.Value, reward);
|
|
data.Progress = json.ToJson();
|
|
await db.SaveChangesAsync();
|
|
return Ok(new OkResponse { Success = true });
|
|
}
|
|
|
|
await PushRewardSelectionAsync(userId.Value, reward);
|
|
|
|
return Ok(new OkResponse { Success = true });
|
|
}
|
|
|
|
private async Task DeliverQuestRewardAsync(long playerId, GameReward reward)
|
|
{
|
|
var box = reward.GiftDrop1;
|
|
if (box == null) return;
|
|
if (box.GiftDropId == 0) box.GiftDropId = StorefrontShared.NewGiftDropId();
|
|
|
|
var already = await GameRewardRedemption.TakeRecentlyHandedOverAsync(
|
|
db, playerId, reward.GiftContext, TimeSpan.FromMinutes(10));
|
|
|
|
if (already == null)
|
|
await StorefrontShared.PersistGiftBoxAsync(db, playerId, box, notify: true);
|
|
reward.Consumed = true;
|
|
reward.ConsumedGiftDropId = already?.GiftDropId ?? box.GiftDropId;
|
|
reward.ConsumedAt = DateTime.UtcNow;
|
|
RewardTrace.Log(playerId, $"stage=DeliveredAsQuestBox context={RewardTrace.Context(reward.GiftContext)} " +
|
|
$"selectionId={reward.RewardSelectionId} alreadyHandedOver={already != null} box={RewardTrace.Box(box)} " +
|
|
"a quest's reward is the box its run generated, not a selection.");
|
|
}
|
|
|
|
private static string CompletionMessage(RewardType rewardType, string? claimed) => rewardType switch
|
|
{
|
|
RewardType.FirstActivityOfDay => "First Game of the Day",
|
|
RewardType.PostQuestActivity => string.IsNullOrWhiteSpace(claimed) ? "Quest Complete" : claimed,
|
|
_ => "Game Reward"
|
|
};
|
|
|
|
private async Task<GameReward> BuildDailyChallengeRewardAsync(long playerId, GameRewardRequestDTO dto)
|
|
{
|
|
var owned = await LevelUpGiftService.CollectOwnedKeysAsync(db, playerId, GiftContext.Daily_Challenge_Complete);
|
|
var drops = RroRewardPools.BuildDailyChallengeSelection(owned: owned);
|
|
|
|
if (drops.Count == 0)
|
|
{
|
|
return BuildReward(dto.RewardType, dto.Message, GiftContext.Daily_Challenge_Complete);
|
|
}
|
|
|
|
foreach (var drop in drops)
|
|
drop.GiftDropId = StorefrontShared.NewGiftDropId();
|
|
|
|
return new GameReward
|
|
{
|
|
RewardSelectionId = StorefrontShared.NewGiftDropId(),
|
|
Message = string.IsNullOrWhiteSpace(dto.Message) ? "Daily Challenge Reward" : dto.Message,
|
|
GiftContext = GiftContext.Daily_Challenge_Complete,
|
|
RewardType = dto.RewardType,
|
|
GiftDrop1 = drops[0],
|
|
GiftDrop2 = drops.Count > 1 ? drops[1] : drops[0],
|
|
GiftDrop3 = drops.Count > 2 ? drops[2] : drops[0],
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
[HttpPost("select"), Authorize]
|
|
public async Task<ActionResult<OkResponse>> SelectReward()
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var dto = await ReadSelectBodyAsync();
|
|
if (dto == null || dto.RewardSelectionId <= 0 || dto.GiftDropId <= 0) return BadRequest();
|
|
|
|
var data = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == userId.Value);
|
|
if (data == null) return NotFound();
|
|
|
|
var json = data.GetProgressionJson();
|
|
var reward = json?.PendingGameRewards?.FirstOrDefault(r => r.RewardSelectionId == dto.RewardSelectionId);
|
|
if (reward == null) return NotFound();
|
|
|
|
var drops = new[] { reward.GiftDrop1, reward.GiftDrop2, reward.GiftDrop3 }
|
|
.Where(g => g != null)
|
|
.ToList();
|
|
|
|
if (reward.Consumed)
|
|
{
|
|
return Ok(new OkResponse { Success = true });
|
|
}
|
|
|
|
var gift = drops.FirstOrDefault(g => g.GiftDropId == dto.GiftDropId);
|
|
|
|
if (gift == null)
|
|
{
|
|
var available = string.Join(", ", drops.Select(d => d.GiftDropId));
|
|
gift = drops.FirstOrDefault();
|
|
if (gift == null) return NotFound();
|
|
}
|
|
|
|
await StorefrontShared.PersistGiftBoxAsync(db, userId.Value, gift, notify: true);
|
|
|
|
reward.Consumed = true;
|
|
reward.ConsumedGiftDropId = gift.GiftDropId;
|
|
reward.ConsumedAt = DateTime.UtcNow;
|
|
data.Progress = json!.ToJson();
|
|
await db.SaveChangesAsync();
|
|
return Ok(new OkResponse { Success = true });
|
|
}
|
|
|
|
private async Task<GameRewardRequestDTO?> ReadRewardBodyAsync()
|
|
{
|
|
var fields = await ReadBodyFieldsAsync();
|
|
if (fields.Count == 0) return null;
|
|
|
|
var dto = new GameRewardRequestDTO();
|
|
if (fields.TryGetValue("rewardType", out var rewardTypeRaw))
|
|
{
|
|
if (int.TryParse(rewardTypeRaw, out var rewardTypeInt) && Enum.IsDefined(typeof(RewardType), rewardTypeInt))
|
|
dto.RewardType = (RewardType)rewardTypeInt;
|
|
else if (Enum.TryParse<RewardType>(rewardTypeRaw, true, out var rewardTypeEnum))
|
|
dto.RewardType = rewardTypeEnum;
|
|
}
|
|
if (fields.TryGetValue("Message", out var message))
|
|
dto.Message = message;
|
|
dto.GiftContext = ReadClaimedContext(fields);
|
|
|
|
return dto;
|
|
}
|
|
|
|
private static GiftContext? ReadClaimedContext(IReadOnlyDictionary<string, string> fields)
|
|
{
|
|
if (!fields.TryGetValue("giftContext", out var raw) || string.IsNullOrWhiteSpace(raw)) return null;
|
|
|
|
if (int.TryParse(raw, out var numeric) && Enum.IsDefined(typeof(GiftContext), numeric))
|
|
return (GiftContext)numeric;
|
|
|
|
return Enum.TryParse<GiftContext>(raw.Trim(), true, out var named) ? named : null;
|
|
}
|
|
|
|
private async Task<GameRewardSelectDTO?> ReadSelectBodyAsync()
|
|
{
|
|
var fields = await ReadBodyFieldsAsync();
|
|
if (fields.Count == 0) return null;
|
|
|
|
var dto = new GameRewardSelectDTO();
|
|
if (fields.TryGetValue("rewardSelectionId", out var selectionRaw) && long.TryParse(selectionRaw, out var selectionId))
|
|
dto.RewardSelectionId = selectionId;
|
|
if (fields.TryGetValue("giftDropId", out var dropRaw) && long.TryParse(dropRaw, out var dropId))
|
|
dto.GiftDropId = dropId;
|
|
|
|
return dto;
|
|
}
|
|
|
|
private async Task<Dictionary<string, string>> ReadBodyFieldsAsync()
|
|
{
|
|
var fields = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
try
|
|
{
|
|
Request.EnableBuffering();
|
|
using var reader = new StreamReader(Request.Body, leaveOpen: true);
|
|
var raw = (await reader.ReadToEndAsync()).Trim();
|
|
Request.Body.Position = 0;
|
|
_rawBody = raw;
|
|
if (raw.Length == 0) return fields;
|
|
|
|
if (raw.StartsWith('{'))
|
|
{
|
|
using var doc = System.Text.Json.JsonDocument.Parse(raw);
|
|
if (doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object)
|
|
{
|
|
foreach (var prop in doc.RootElement.EnumerateObject())
|
|
{
|
|
fields[prop.Name] = prop.Value.ValueKind == System.Text.Json.JsonValueKind.String
|
|
? prop.Value.GetString() ?? ""
|
|
: prop.Value.ToString();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var pair in raw.Split('&'))
|
|
{
|
|
var kv = pair.Split('=', 2);
|
|
if (kv.Length < 2) continue;
|
|
fields[Uri.UnescapeDataString(kv[0])] = Uri.UnescapeDataString(kv[1]);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
private async Task<GameReward> BuildRewardAsync(long playerId, GameRewardRequestDTO dto, RewardType rewardType)
|
|
{
|
|
var lastRoom = await MatchmakingUtils.GetLastVisitedRoomNameAsync(db, playerId);
|
|
var roomName = lastRoom;
|
|
var claimedContext = dto.GiftContext;
|
|
if (claimedContext.HasValue
|
|
&& RroRewardPools.RoomForContext(claimedContext.Value) is { } claimedRoom
|
|
&& !RroRewardPools.ContextsForRoom(roomName).Values.Contains(claimedContext.Value))
|
|
{
|
|
roomName = claimedRoom;
|
|
}
|
|
|
|
if (!RroRewardPools.IsRewardActivity(roomName)
|
|
&& await MatchmakingUtils.GetLastRewardActivityRoomNameAsync(db, playerId, ActivityVisitWindow) is { } activityRoom)
|
|
{
|
|
Trace(playerId, $"lastRoom={lastRoom} is not an activity this server pays; {activityRoom} is the newest activity they were placed into, so that is the activity this completion is answered from.");
|
|
roomName = activityRoom;
|
|
}
|
|
|
|
var paidByRoom = RroRewardPools.ContextsForRoom(roomName);
|
|
var paidList = string.Join(", ", paidByRoom.Select(x => x.Key + "=" + (int)x.Value));
|
|
Trace(playerId, "rewardType=" + rewardType
|
|
+ " claimedContext=" + dto.GiftContext + " lastRoom=" + lastRoom
|
|
+ " activity=" + roomName
|
|
+ " isRewardActivity=" + RroRewardPools.IsRewardActivity(roomName) + " roomPays=[" + paidList + "]");
|
|
|
|
if (!RroRewardPools.IsRewardActivity(roomName))
|
|
{
|
|
Trace(playerId, "room=" + roomName + " is not an activity this server pays rewards for; paying the token package.");
|
|
return BuildReward(rewardType, dto.Message, dto.GiftContext ?? GiftContext.GameRewards_Tokens);
|
|
}
|
|
|
|
var paid = RroRewardPools.ContextsForRoom(roomName);
|
|
var claimed = dto.GiftContext;
|
|
GiftContext context;
|
|
if (claimed.HasValue && paid.Values.Contains(claimed.Value))
|
|
{
|
|
context = claimed.Value;
|
|
}
|
|
else if (RroRewardPools.TryFloorContext(roomName, out var floor))
|
|
{
|
|
context = floor;
|
|
}
|
|
else
|
|
{
|
|
return BuildReward(rewardType, dto.Message, GiftContext.GameRewards_Tokens);
|
|
}
|
|
|
|
if (RroRewardPools.PaysTickets(roomName))
|
|
{
|
|
var games = LaserTagGamesToday.AddOrUpdate((playerId, DateOnly.FromDateTime(DateTime.UtcNow)), 1, (_, count) => count + 1);
|
|
var tickets = RroRewardPools.LaserTagTicketsPerGame
|
|
+ (games <= RroRewardPools.LaserTagBonusGamesPerDay ? RroRewardPools.LaserTagFirstGamesBonus : 0);
|
|
return TicketReward(rewardType, dto.Message, GiftContext.Lasertag, tickets);
|
|
}
|
|
|
|
var selection = RroRewardPools.OffersSelection(roomName);
|
|
var owned = await LevelUpGiftService.CollectOwnedKeysAsync(db, playerId, context);
|
|
|
|
var fromOwnPool = !RroRewardPools.PaysNoItemReward(roomName)
|
|
&& Random.Shared.NextDouble() <= RroRewardPools.DropChanceFor(context);
|
|
var drops = fromOwnPool
|
|
? RroRewardPools.BuildDrops(context, selection, owned)
|
|
: new List<Gift>();
|
|
|
|
var source = context;
|
|
|
|
var reacquired = drops.Count == 0;
|
|
if (reacquired)
|
|
{
|
|
foreach (var weaker in RroRewardPools.LowerRanksOf(context))
|
|
{
|
|
drops = RroRewardPools.BuildDrops(weaker, selection, owned);
|
|
if (drops.Count == 0) continue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (drops.Count == 0)
|
|
{
|
|
if (selection)
|
|
{
|
|
drops = RroRewardPools.BuildFallbackSelection(owned: owned, message: dto.Message);
|
|
}
|
|
else
|
|
{
|
|
drops = RroRewardPools.BuildFallbackDrops(selection: false, owned: owned);
|
|
}
|
|
}
|
|
|
|
if (drops.Count == 0)
|
|
{
|
|
RewardTrace.Error(playerId, $"stage=Fallback activity={roomName} rank={RewardRankOf(context, roomName)} " +
|
|
$"context={RewardTrace.Context(context)} pool={DescribeSets(context)} " +
|
|
$"EligibleItems={RroRewardPools.Resolve(context).Count} UnownedItems=0 " +
|
|
"Reason=neither the activity's pool nor the reacquisition pool has anything left to give; " +
|
|
"paying the token package as the last resort.");
|
|
return BuildReward(rewardType, dto.Message, context);
|
|
}
|
|
|
|
foreach (var drop in drops)
|
|
drop.GiftDropId = StorefrontShared.NewGiftDropId();
|
|
|
|
var reward = new GameReward
|
|
{
|
|
RewardSelectionId = StorefrontShared.NewGiftDropId(),
|
|
Message = string.IsNullOrWhiteSpace(dto.Message) ? "Game Reward" : dto.Message,
|
|
GiftContext = source,
|
|
RewardType = rewardType,
|
|
GiftDrop1 = drops[0],
|
|
GiftDrop2 = drops.Count > 1 ? drops[1] : drops[0],
|
|
GiftDrop3 = drops.Count > 2 ? drops[2] : drops[0],
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
var eligible = RroRewardPools.Resolve(context);
|
|
var unowned = eligible
|
|
.Where(item => !owned.Contains(LevelUpRewardPool.OwnershipKey(item.AvatarItemDesc)))
|
|
.ToList();
|
|
var boxKind = IsTokenBox(drops[0]) ? "Tokens" : "AvatarItem";
|
|
|
|
RewardTrace.Log(playerId, $"stage=Quest activity={roomName} rank={RewardRankOf(context, roomName)} " +
|
|
$"context={RewardTrace.Context(context)} source={RewardTrace.Context(source)} rewardType={rewardType}");
|
|
RewardTrace.Log(playerId, $"stage=RewardPool context={RewardTrace.Context(context)} " +
|
|
$"pool={DescribeSets(context)} EligibleItems={eligible.Count} {RewardTrace.Items(eligible)}");
|
|
RewardTrace.Log(playerId, $"stage=UnownedItems context={RewardTrace.Context(context)} UnownedItems={unowned.Count} {RewardTrace.Items(unowned)}");
|
|
RewardTrace.Log(playerId, $"stage=SelectedItem {RewardTrace.Boxes(drops)}");
|
|
RewardTrace.Log(playerId, $"stage=BoxCreated selectionId={reward.RewardSelectionId} " +
|
|
$"boxType={boxKind} BoxRewardItem=\"{drops[0].AvatarItemDesc}\"");
|
|
RewardTrace.Log(playerId, $"stage=GrantReward giftDropId={drops[0].GiftDropId} " +
|
|
$"name=\"{drops[0].FriendlyName}\" item=\"{drops[0].AvatarItemDesc}\" " +
|
|
$"consumable=\"{drops[0].ConsumableItemDesc}\" currency={drops[0].CurrencyType} {drops[0].Currency}");
|
|
RewardTrace.Log(playerId, $"stage=FinalReward rewardType={rewardType} context={RewardTrace.Context(source)} " +
|
|
$"boxType={boxKind} firstTime={!reacquired}");
|
|
|
|
return reward;
|
|
}
|
|
|
|
private static readonly ConcurrentDictionary<(long PlayerId, DateOnly Day), int> LaserTagGamesToday = new();
|
|
|
|
private static GameReward TicketReward(RewardType type, string message, GiftContext context, int tickets)
|
|
{
|
|
var box = new Gift
|
|
{
|
|
GiftDropId = StorefrontShared.NewGiftDropId(),
|
|
FriendlyName = "Laser Tag Tickets",
|
|
Tooltip = string.IsNullOrWhiteSpace(message) ? "Tickets for playing!" : message,
|
|
CurrencyType = CurrencyType.LaserTagTickets,
|
|
Currency = tickets,
|
|
Context = context,
|
|
Rarity = Rarity.Common
|
|
};
|
|
|
|
return new GameReward
|
|
{
|
|
RewardSelectionId = StorefrontShared.NewGiftDropId(),
|
|
Message = string.IsNullOrWhiteSpace(message) ? "Laser Tag Tickets" : message,
|
|
GiftContext = context,
|
|
RewardType = type,
|
|
GiftDrop1 = box,
|
|
GiftDrop2 = box,
|
|
GiftDrop3 = box,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
private static bool IsCompletion(RewardType type) =>
|
|
type is RewardType.FirstActivityOfDay or RewardType.PostGameActivity or RewardType.PostQuestActivity;
|
|
|
|
private static GiftContext? BonusContext(RewardType type) => type switch
|
|
{
|
|
RewardType.DailyChallengesComplete => GiftContext.All_Daily_Challenges_Complete,
|
|
RewardType.WeeklyCompleteButHaveSkin => GiftContext.All_Weekly_Challenge_Complete,
|
|
RewardType.Levelup => GiftContext.LevelUp,
|
|
_ => null
|
|
};
|
|
|
|
private static GameReward BuildReward(RewardType type, string message, GiftContext context)
|
|
{
|
|
int baseTokens = type switch
|
|
{
|
|
RewardType.DailyChallengesComplete => 150,
|
|
RewardType.WeeklyCompleteButHaveSkin => 300,
|
|
RewardType.Levelup => 100,
|
|
_ => RroRewardPools.CompletionTokenAmount
|
|
};
|
|
|
|
return new GameReward
|
|
{
|
|
RewardSelectionId = StorefrontShared.NewGiftDropId(),
|
|
Message = string.IsNullOrWhiteSpace(message) ? "Tokens" : message,
|
|
GiftContext = context,
|
|
RewardType = type,
|
|
GiftDrop1 = RroRewardPools.TokenGift(context, baseTokens, message),
|
|
GiftDrop2 = RroRewardPools.TokenGift(context, baseTokens * 2, message),
|
|
GiftDrop3 = RroRewardPools.TokenGift(context, baseTokens * 3, message),
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
private static async Task PushRewardSelectionAsync(long playerId, GameReward reward)
|
|
{
|
|
var number = Convert.ToInt64(
|
|
RadicadeServer.RecNet.Enums.Messages.NotificationType.GiftPackageRewardSelectionReceived,
|
|
System.Globalization.CultureInfo.InvariantCulture)
|
|
.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
|
|
|
foreach (var id in new[] { RewardSelectionNotificationName, number })
|
|
{
|
|
try
|
|
{
|
|
await RadicadeServer.API.Notifications.Notify.SendNotify(playerId, new RadicadeServer.RecNet.Notifications.Notification
|
|
{
|
|
Id = id,
|
|
Msg = reward
|
|
});
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly TimeSpan ActivityVisitWindow = TimeSpan.FromHours(6);
|
|
|
|
private string? _rawBody;
|
|
|
|
private static void Trace(long playerId, string line) => RewardTrace.Log(playerId, line);
|
|
|
|
private static string RewardRankOf(GiftContext context, string? roomName)
|
|
{
|
|
foreach (var (rank, paid) in RroRewardPools.ContextsForRoom(roomName))
|
|
{
|
|
if (paid != context) continue;
|
|
return string.IsNullOrEmpty(rank) ? "unranked" : rank;
|
|
}
|
|
|
|
return RroRewardPools.IsDeprecated(context) ? "deprecated" : "not paid by this room";
|
|
}
|
|
|
|
private static string DescribeSets(GiftContext context)
|
|
{
|
|
var sets = RroRewardPools.SetsFor(context);
|
|
if (sets.Count == 0) return "<none configured>";
|
|
|
|
return string.Join(", ", sets.Select(set =>
|
|
string.IsNullOrEmpty(set.Colour) ? set.Family : $"{set.Family} {set.Colour}"));
|
|
}
|
|
|
|
private static bool IsTokenBox(Gift box) =>
|
|
string.IsNullOrWhiteSpace(box.AvatarItemDesc)
|
|
&& string.IsNullOrWhiteSpace(box.ConsumableItemDesc)
|
|
&& box.CurrencyType != CurrencyType.Invalid
|
|
&& box.Currency != 0;
|
|
|
|
private const string RewardSelectionNotificationName = "RewardSelectionReceived";
|
|
}
|
|
}
|