mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,546 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
using RadicadeServer.API.Storefront;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Extensions;
|
||||
using RadicadeServer.RecNet.Accounts;
|
||||
using RadicadeServer.Utils;
|
||||
using RadicadeServer.RecNet.Avatar;
|
||||
using RadicadeServer.RecNet.Enums.Store;
|
||||
using RadicadeServer.RecNet.Progression;
|
||||
using RadicadeServer.RecNet.Store;
|
||||
|
||||
namespace RadicadeServer.API.Avatars.V2;
|
||||
|
||||
[Route("api/avatar/v2")]
|
||||
[Route("econ/api/avatar/v2")]
|
||||
public class AvatarV2(RecDBContext db) : Controller
|
||||
{
|
||||
[HttpGet(""), Authorize]
|
||||
public async Task<Avatar?> GetLocalAvatar()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return null;
|
||||
|
||||
var avatar = db.Avatars.FirstOrDefault(x => x.AccountId == userId);
|
||||
if (avatar != null)
|
||||
{
|
||||
if (NormalizeSavedAvatar(avatar))
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return avatar.DeserializeAvatar();
|
||||
}
|
||||
|
||||
avatar = new RadicadeServer.RecNet.Accounts.Local.PlayerAvatar
|
||||
{
|
||||
AccountId = userId.Value,
|
||||
Avatar = DBConfig.DefaultAvatar,
|
||||
Outfits = "[]"
|
||||
};
|
||||
await db.Avatars.AddAsync(avatar);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return avatar.DeserializeAvatar();
|
||||
}
|
||||
|
||||
[HttpGet("{accountId:long}")]
|
||||
public async Task<Avatar> GetAvatarForAccount(long accountId)
|
||||
{
|
||||
var saved = await db.Avatars.AsNoTracking().FirstOrDefaultAsync(x => x.AccountId == accountId);
|
||||
var avatar = saved?.DeserializeAvatar();
|
||||
if (avatar != null) return avatar;
|
||||
|
||||
return DBConfig.DefaultAvatar.FromJson<Avatar>() ?? new Avatar();
|
||||
}
|
||||
|
||||
[HttpPost("set"), Authorize]
|
||||
public async Task<Avatar?> SetLocalAvatar([FromBody] Avatar newAvatar)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return null;
|
||||
|
||||
var avatar = await db.Avatars.FirstOrDefaultAsync(x => x.AccountId == userId);
|
||||
if (avatar == null)
|
||||
{
|
||||
avatar = new RadicadeServer.RecNet.Accounts.Local.PlayerAvatar
|
||||
{
|
||||
AccountId = userId.Value,
|
||||
Avatar = DBConfig.DefaultAvatar,
|
||||
Outfits = "[]"
|
||||
};
|
||||
await db.Avatars.AddAsync(avatar);
|
||||
}
|
||||
|
||||
avatar.Avatar = newAvatar.ToJson();
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return newAvatar;
|
||||
} [HttpGet("gifts"), Authorize]
|
||||
public async Task<IActionResult> GetGifts()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var gifts = await StorefrontShared.GetPendingGiftsAsync(db, userId.Value);
|
||||
|
||||
return Ok(gifts.Select(g => g.ForClient()).ToList());
|
||||
}
|
||||
|
||||
[HttpPost("gifts/claim"), Authorize]
|
||||
public async Task<IActionResult> ClaimGifts()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var requestedIds = await ReadGiftIdsFromBodyAsync();
|
||||
|
||||
var gifts = await StorefrontShared.GetPendingGiftsAsync(db, userId.Value);
|
||||
var toClaim = requestedIds.Count > 0
|
||||
? gifts.Where(g => requestedIds.Contains(g.GiftDropId)).ToList()
|
||||
: gifts;
|
||||
|
||||
if (toClaim.Count == 0)
|
||||
{
|
||||
return GiftClaimResponse(new List<Gift>(), await GetPlayerLevelAsync(userId.Value));
|
||||
}
|
||||
|
||||
var claimed = new List<Gift>();
|
||||
var levelUpGifts = new List<Gift>();
|
||||
var balanceGrants = new List<StorefrontShared.BalanceGrant>();
|
||||
var strategy = db.Database.CreateExecutionStrategy();
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
await using var tx = await db.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
foreach (var gift in toClaim)
|
||||
{
|
||||
var grant = await GrantGift(userId.Value, gift);
|
||||
if (grant != null) balanceGrants.Add(grant);
|
||||
await StorefrontShared.RemovePendingGiftAsync(db, userId.Value, gift.GiftDropId);
|
||||
claimed.Add(gift);
|
||||
}
|
||||
levelUpGifts.AddRange(await LevelUpGiftService.DeliverLevelUpGiftsAsync(db, userId.Value, recomputeFromXp: true, notify: false));
|
||||
await tx.CommitAsync();
|
||||
await AnnounceBalancesAsync(userId.Value, balanceGrants, StorefrontBalanceAddTypes.FromGiftBox);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await tx.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var gift in levelUpGifts)
|
||||
await StorefrontShared.SendGiftPackageNotificationAsync(userId.Value, gift);
|
||||
return GiftClaimResponse(claimed, await GetPlayerLevelAsync(userId.Value));
|
||||
}
|
||||
|
||||
[HttpPost("gifts/generate"), Authorize]
|
||||
public async Task<IActionResult> GenerateGift()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var fields = await ReadGiftGenerateFieldsAsync();
|
||||
if (!TryReadGiftContext(fields, "MFPELEJLJLD", "GiftContext", out var giftContext) ||
|
||||
!TryReadOptionalGiftContext(fields, "CIEHFJEIOJJ", "AlternateGiftContext", out var alternateContext))
|
||||
return BadRequest(new { error = "invalid_gift_context" });
|
||||
|
||||
fields.TryGetValue("Message", out var message);
|
||||
if (message == null && fields.TryGetValue("DPAJDIEBNKM", out var opaqueMessage))
|
||||
message = opaqueMessage;
|
||||
var xp = TryFindField(fields, out var rawXp, "Xp", "XP") && int.TryParse(rawXp, out var parsedXp)
|
||||
? parsedXp
|
||||
: 0;
|
||||
|
||||
var context = (GiftContext)giftContext;
|
||||
if (context is GiftContext.All_Weekly_Challenge_Complete or GiftContext.Weekly_Challenge_Complete)
|
||||
{
|
||||
var mapId = WeeklyChallenges.CreateMap(DateTime.UtcNow).ChallengeMapId;
|
||||
var state = await db.WeeklyChallenges.AsNoTracking().SingleOrDefaultAsync(x =>
|
||||
x.PlayerId == userId.Value && x.ChallengeMapId == mapId);
|
||||
var pendingWeekly = await StorefrontShared.GetPendingGiftsAsync(db, userId.Value);
|
||||
var weeklyGift = pendingWeekly.FirstOrDefault(x => x.GiftDropId == state?.RewardGiftDropId);
|
||||
return weeklyGift == null
|
||||
? Conflict(new { error = "weekly_reward_not_pending" })
|
||||
: Ok(weeklyGift.ForClient());
|
||||
}
|
||||
void Trace(string line) => RewardTrace.Log(userId.Value, line);
|
||||
|
||||
var room = RroRewardPools.RoomForContext(context);
|
||||
var paysReward = room != null && RroRewardPools.IsRewardActivity(room);
|
||||
RewardTrace.Log(userId.Value, $"stage=Generate claimed={RewardTrace.Context(context)} " +
|
||||
$"room={(room ?? "<none>")} isActivityReward={paysReward} xp={xp} message=\"{message}\"");
|
||||
|
||||
var gift = await GameRewardRedemption.TakeOutstandingAsync(db, userId.Value, context, message, Trace);
|
||||
var source = gift == null ? null : "outstanding reward";
|
||||
|
||||
if (gift == null)
|
||||
{
|
||||
gift = await GameRewardRedemption.TakePendingBoxAsync(db, userId.Value, context, Trace);
|
||||
if (gift != null) source = "box the selection route already handed over";
|
||||
}
|
||||
|
||||
if (gift == null)
|
||||
{
|
||||
gift = await GameRewardRedemption.TakeRecentlyHandedOverAsync(
|
||||
db, userId.Value, context, TimeSpan.FromMinutes(10), Trace);
|
||||
if (gift != null) source = "box the server already handed over for this reward";
|
||||
}
|
||||
|
||||
if (gift == null && paysReward)
|
||||
{
|
||||
gift = await GameRewardRedemption.BuildRoomRewardAsync(db, userId.Value, context, message, Trace);
|
||||
if (gift != null)
|
||||
source = "the activity's own reward, selected by the server";
|
||||
else
|
||||
{
|
||||
var sets = string.Join(", ", RroRewardPools.SetsFor(context)
|
||||
.Select(x => string.IsNullOrEmpty(x.Colour) ? x.Family : $"{x.Family} {x.Colour}"));
|
||||
RewardTrace.Error(userId.Value,
|
||||
"Unable to resolve the reward for this context. " +
|
||||
$"Activity={room} Context={RewardTrace.Context(context)} ExpectedPool={sets} " +
|
||||
$"EligibleItems={RroRewardPools.Resolve(context).Count} " +
|
||||
"Reason=the activity's own pool, its lower ranks and the non-exclusive pool all " +
|
||||
"resolved to nothing.");
|
||||
}
|
||||
}
|
||||
|
||||
if (gift == null)
|
||||
{
|
||||
RewardTrace.Log(userId.Value, $"stage=Fallback claimed={RewardTrace.Context(context)} " +
|
||||
"is not an activity this server pays a reward for; generating the ordinary box.");
|
||||
gift = BuildGeneratedGift(userId.Value, context, message, xp);
|
||||
source = "generated box (no activity reward)";
|
||||
}
|
||||
|
||||
await StorefrontShared.PersistGiftBoxAsync(db, userId.Value, gift, notify: false);
|
||||
RewardTrace.Log(userId.Value, $"stage=Grant source={source} box={RewardTrace.Box(gift)}");
|
||||
return Ok(gift.ForClient());
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> ReadGiftGenerateFieldsAsync()
|
||||
{
|
||||
var fields = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (Request.HasFormContentType)
|
||||
{
|
||||
var form = await Request.ReadFormAsync();
|
||||
foreach (var field in form) fields[field.Key] = field.Value.FirstOrDefault() ?? string.Empty;
|
||||
return fields;
|
||||
}
|
||||
|
||||
Request.EnableBuffering();
|
||||
using var reader = new StreamReader(Request.Body, leaveOpen: true);
|
||||
var raw = (await reader.ReadToEndAsync()).Trim();
|
||||
Request.Body.Position = 0;
|
||||
if (raw.Length == 0) return fields;
|
||||
|
||||
if (raw.StartsWith('{'))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(raw);
|
||||
foreach (var property in document.RootElement.EnumerateObject())
|
||||
fields[property.Name] = property.Value.ValueKind == JsonValueKind.String
|
||||
? property.Value.GetString() ?? string.Empty
|
||||
: property.Value.ToString();
|
||||
}
|
||||
catch (JsonException) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var pair in raw.Split('&', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var parts = pair.Split('=', 2);
|
||||
fields[Uri.UnescapeDataString(parts[0].Replace('+', ' '))] =
|
||||
parts.Length > 1 ? Uri.UnescapeDataString(parts[1].Replace('+', ' ')) : string.Empty;
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static bool TryReadGiftContext(IReadOnlyDictionary<string, string> fields, string opaqueName, string friendlyName, out int value)
|
||||
{
|
||||
value = 0;
|
||||
if (!TryFindField(fields, out var raw, opaqueName, friendlyName)) return false;
|
||||
if (!int.TryParse(raw, out value)) return false;
|
||||
return Enum.IsDefined(typeof(GiftContext), value) && (GiftContext)value != GiftContext.None;
|
||||
}
|
||||
|
||||
private static bool TryReadOptionalGiftContext(IReadOnlyDictionary<string, string> fields, string opaqueName, string friendlyName, out int? value)
|
||||
{
|
||||
value = null;
|
||||
if (!TryFindField(fields, out var raw, opaqueName, friendlyName) || string.IsNullOrWhiteSpace(raw)) return true;
|
||||
if (!int.TryParse(raw, out var parsed) || !Enum.IsDefined(typeof(GiftContext), parsed)) return false;
|
||||
value = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryFindField(IReadOnlyDictionary<string, string> fields, out string raw, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (fields.TryGetValue(name, out var value))
|
||||
{
|
||||
raw = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
raw = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Gift BuildGeneratedGift(long playerId, GiftContext context, string? message, int xp)
|
||||
{
|
||||
int[] tokenAmounts = [10, 25, 50, 100, 250, 500];
|
||||
return new Gift
|
||||
{
|
||||
GiftDropId = StorefrontShared.NewGiftDropId(),
|
||||
FromPlayerId = playerId,
|
||||
Tooltip = message ?? string.Empty,
|
||||
CurrencyType = CurrencyType.RecCenterTokens,
|
||||
Currency = tokenAmounts[Random.Shared.Next(tokenAmounts.Length)],
|
||||
Xp = xp,
|
||||
Context = context,
|
||||
BalanceType = 0,
|
||||
Rarity = Rarity.Rare
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("gifts/consume/{giftDropId:long}"), Authorize]
|
||||
public async Task<IActionResult> ConsumeGift(long giftDropId)
|
||||
=> await ConsumePlayerGift(giftDropId, await ReadGiftIdsFromBodyAsync());
|
||||
|
||||
[HttpPost("gifts/consume"), Authorize]
|
||||
public async Task<IActionResult> ConsumeGiftBody()
|
||||
=> await ConsumePlayerGift(null, await ReadGiftIdsFromBodyAsync());
|
||||
|
||||
private async Task<List<long>> ReadGiftIdsFromBodyAsync()
|
||||
{
|
||||
var ids = new List<long>();
|
||||
try
|
||||
{
|
||||
Request.EnableBuffering();
|
||||
using var reader = new StreamReader(Request.Body, leaveOpen: true);
|
||||
var raw = (await reader.ReadToEndAsync()).Trim();
|
||||
Request.Body.Position = 0;
|
||||
if (raw.Length == 0) return ids;
|
||||
|
||||
if (raw.StartsWith('{'))
|
||||
{
|
||||
using var doc = JsonDocument.Parse(raw);
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
if (!string.Equals(prop.Name, "Id", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(prop.Name, "giftDropId", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (TryReadGiftId(prop.Value, out var v)) ids.Add(v);
|
||||
}
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
if (!string.Equals(prop.Name, "giftDropIds", StringComparison.OrdinalIgnoreCase) ||
|
||||
prop.Value.ValueKind != JsonValueKind.Array)
|
||||
continue;
|
||||
foreach (var el in prop.Value.EnumerateArray())
|
||||
if (TryReadGiftId(el, out var v)) ids.Add(v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var pair in raw.Split('&'))
|
||||
{
|
||||
var kv = pair.Split('=', 2);
|
||||
var key = Uri.UnescapeDataString(kv[0]).Trim();
|
||||
if (!string.Equals(key, "Id", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(key, "giftDropId", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
var val = kv.Length > 1 ? Uri.UnescapeDataString(kv[1]).Trim() : "";
|
||||
if (long.TryParse(val, out var v)) ids.Add(v);
|
||||
}
|
||||
if (ids.Count == 0 && long.TryParse(raw, out var bare)) ids.Add(bare);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return ids.Distinct().ToList();
|
||||
}
|
||||
|
||||
private static bool TryReadGiftId(JsonElement el, out long v)
|
||||
{
|
||||
if (el.ValueKind == JsonValueKind.Number && el.TryGetInt64(out v)) return true;
|
||||
if (el.ValueKind == JsonValueKind.String && long.TryParse(el.GetString(), out v)) return true;
|
||||
v = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<IActionResult> ConsumePlayerGift(long? giftDropId, List<long> bodyIds)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == null) return Unauthorized();
|
||||
|
||||
var requestedIds = new List<long>();
|
||||
if (giftDropId is > 0) requestedIds.Add(giftDropId.Value);
|
||||
if (bodyIds is { Count: > 0 }) requestedIds.AddRange(bodyIds);
|
||||
|
||||
var gifts = await StorefrontShared.GetPendingGiftsAsync(db, userId.Value);
|
||||
var toConsume = requestedIds.Count > 0
|
||||
? gifts.Where(g => requestedIds.Contains(g.GiftDropId)).ToList()
|
||||
: gifts;
|
||||
if (toConsume.Count == 0)
|
||||
{
|
||||
var delivered = requestedIds.Count > 0
|
||||
? await HandedOverBoxIdsAsync(userId.Value, requestedIds)
|
||||
: new List<long>();
|
||||
if (delivered.Count > 0)
|
||||
{
|
||||
return GiftClaimResponse(new List<Gift>(), await GetPlayerLevelAsync(userId.Value));
|
||||
}
|
||||
return NotFound(new { error = "unable_to_consume_missing_or_used_gift", giftDropId });
|
||||
}
|
||||
|
||||
var consumed = new List<Gift>();
|
||||
var levelUpGifts = new List<Gift>();
|
||||
var balanceGrants = new List<StorefrontShared.BalanceGrant>();
|
||||
var strategy = db.Database.CreateExecutionStrategy();
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
await using var tx = await db.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
foreach (var gift in toConsume)
|
||||
{
|
||||
var grant = await GrantGift(userId.Value, gift);
|
||||
if (grant != null) balanceGrants.Add(grant);
|
||||
await StorefrontShared.RemovePendingGiftAsync(db, userId.Value, gift.GiftDropId);
|
||||
consumed.Add(gift);
|
||||
}
|
||||
levelUpGifts.AddRange(await LevelUpGiftService.DeliverLevelUpGiftsAsync(db, userId.Value, recomputeFromXp: true, notify: false));
|
||||
await tx.CommitAsync();
|
||||
await AnnounceBalancesAsync(userId.Value, balanceGrants, StorefrontBalanceAddTypes.FromGiftBox);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await tx.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var gift in levelUpGifts)
|
||||
await StorefrontShared.SendGiftPackageNotificationAsync(userId.Value, gift);
|
||||
|
||||
if (consumed.Count > 0)
|
||||
await ProgressionAwardService.AwardXpAsync(db, userId.Value,
|
||||
consumed.Count * ProgressionAwardService.XpPerGiftBoxOpened, "gift box");
|
||||
return GiftClaimResponse(consumed, await GetPlayerLevelAsync(userId.Value));
|
||||
}
|
||||
|
||||
private async Task<int> GetPlayerLevelAsync(long playerId)
|
||||
{
|
||||
var progression = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == playerId);
|
||||
return progression?.Level ?? 1;
|
||||
}
|
||||
|
||||
private async Task<List<long>> HandedOverBoxIdsAsync(long playerId, List<long> requestedIds)
|
||||
{
|
||||
var data = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == playerId);
|
||||
var rewards = data?.GetProgressionJson()?.PendingGameRewards;
|
||||
if (rewards == null) return new List<long>();
|
||||
|
||||
return rewards
|
||||
.Where(r => r.ConsumedGiftDropId != 0 && requestedIds.Contains(r.ConsumedGiftDropId))
|
||||
.Select(r => r.ConsumedGiftDropId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private IActionResult GiftClaimResponse(List<Gift> claimed, int unlockedLevel)
|
||||
{
|
||||
var body = new Dictionary<string, object?>
|
||||
{
|
||||
["consumed"] = claimed.Select(g => g.ForClient()).ToList(),
|
||||
["UnlockedLevel"] = unlockedLevel
|
||||
};
|
||||
var first = claimed.FirstOrDefault();
|
||||
if (first != null)
|
||||
{
|
||||
foreach (var kv in first.ForClient().ToJson().FromJson<Dictionary<string, object?>>() ?? new Dictionary<string, object?>())
|
||||
body[kv.Key] = kv.Value;
|
||||
}
|
||||
return Ok(body);
|
||||
}
|
||||
|
||||
private async Task<StorefrontShared.BalanceGrant?> GrantGift(long playerId, Gift gift)
|
||||
{
|
||||
return await StorefrontShared.GrantGiftAsync(db, playerId, gift);
|
||||
}
|
||||
|
||||
private static async Task AnnounceBalancesAsync(long playerId, List<StorefrontShared.BalanceGrant> grants, StorefrontBalanceAddTypes addType)
|
||||
{
|
||||
foreach (var grant in grants)
|
||||
await StorefrontShared.SendBalanceUpdateAsync(playerId, grant.CurrencyType, grant.Balance, grant.Granted, addType);
|
||||
}
|
||||
|
||||
private static bool NormalizeSavedAvatar(RadicadeServer.RecNet.Accounts.Local.PlayerAvatar avatar)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(avatar.Avatar))
|
||||
{
|
||||
avatar.Avatar = DBConfig.DefaultAvatar;
|
||||
return true;
|
||||
}
|
||||
|
||||
var original = avatar.Avatar;
|
||||
avatar.Avatar = RepairKnownUnsafeOutfitSelections(avatar.Avatar);
|
||||
|
||||
return avatar.Avatar != original;
|
||||
}
|
||||
|
||||
private static string RepairKnownUnsafeOutfitSelections(string avatarJson)
|
||||
{
|
||||
const string body = "d0a9262f-5504-46a7-bb10-7507503db58e";
|
||||
const string accessory = "95e4cc30-cb68-473d-a395-feadf5b51512";
|
||||
const string repairedOutfitSelection = body + "," + accessory
|
||||
+ ",0440f08f-ef1d-49d8-942b-523056e8bb45,,1";
|
||||
|
||||
string[] shortOutfitSelections =
|
||||
[
|
||||
body + ",,,,1",
|
||||
body + "," + accessory + ",,,1"
|
||||
];
|
||||
|
||||
foreach (var shortOutfitSelection in shortOutfitSelections)
|
||||
{
|
||||
avatarJson = avatarJson.Replace(
|
||||
shortOutfitSelection,
|
||||
repairedOutfitSelection,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return avatarJson;
|
||||
}
|
||||
}
|
||||
|
||||
public class ClaimGiftsRequest
|
||||
{
|
||||
public List<long>? GiftDropIds { get; set; }
|
||||
public long? GiftDropId { get; set; }
|
||||
}
|
||||
|
||||
public class GenerateGiftRequest
|
||||
{
|
||||
public long? RecipientPlayerId { get; set; }
|
||||
public long? RecipientId { get; set; }
|
||||
public int? PurchasableItemId { get; set; }
|
||||
public int? ItemId { get; set; }
|
||||
public long? GiftDropId { get; set; }
|
||||
public StorefrontTypes? Storefront { get; set; }
|
||||
public CurrencyType? Currency { get; set; }
|
||||
public long? Price { get; set; }
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user