|
@@ -15,6 +15,10 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
public class GameBusinessImpl : IGameBusiness
|
|
public class GameBusinessImpl : IGameBusiness
|
|
|
{
|
|
{
|
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(GameBusinessImpl));
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(GameBusinessImpl));
|
|
|
|
|
+ private const int MinGameLevel = 1;
|
|
|
|
|
+ private const int DbMaxGameLevel = 50;
|
|
|
|
|
+ private const long MaxAttemptScore = int.MaxValue;
|
|
|
|
|
+ private const long MaxTotalScore = 999999999999;
|
|
|
|
|
|
|
|
private readonly ModelContext dbContext;
|
|
private readonly ModelContext dbContext;
|
|
|
private readonly IConfiguration configuration;
|
|
private readonly IConfiguration configuration;
|
|
@@ -85,6 +89,9 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
.Include(x => x.GameRewardLog)
|
|
.Include(x => x.GameRewardLog)
|
|
|
.Where(x => x.Msisdn == msisdn
|
|
.Where(x => x.Msisdn == msisdn
|
|
|
&& x.Status == 2
|
|
&& x.Status == 2
|
|
|
|
|
+ && x.RewardType != null
|
|
|
|
|
+ && x.RewardType != ""
|
|
|
|
|
+ && (x.RewardValue ?? 0) > 0
|
|
|
&& ((x.SubmitTime ?? x.StartTime) >= fromDate)
|
|
&& ((x.SubmitTime ?? x.StartTime) >= fromDate)
|
|
|
&& ((x.SubmitTime ?? x.StartTime) < toExclusive))
|
|
&& ((x.SubmitTime ?? x.StartTime) < toExclusive))
|
|
|
.OrderByDescending(x => x.SubmitTime ?? x.StartTime)
|
|
.OrderByDescending(x => x.SubmitTime ?? x.StartTime)
|
|
@@ -582,9 +589,13 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var now = DateTime.Now;
|
|
var now = DateTime.Now;
|
|
|
- var levelNo = (byte)Math.Clamp(request.level <= 0 ? 1 : request.level, 1, 255);
|
|
|
|
|
var mode = await GetGameModeAsync(request.gameType);
|
|
var mode = await GetGameModeAsync(request.gameType);
|
|
|
if (mode == null) return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game mode is not configured", new { });
|
|
if (mode == null) return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game mode is not configured", new { });
|
|
|
|
|
+ var levelNo = NormalizeRequestedLevel(request.level);
|
|
|
|
|
+ if (levelNo == null || !await IsConfiguredLevelAsync(mode.ModeId, levelNo.Value))
|
|
|
|
|
+ {
|
|
|
|
|
+ return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game level is not configured", new { });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
var account = await dbContext.AccountUsers.AsNoTracking()
|
|
var account = await dbContext.AccountUsers.AsNoTracking()
|
|
|
.Where(x => x.Msisdn == msisdn)
|
|
.Where(x => x.Msisdn == msisdn)
|
|
@@ -604,9 +615,9 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
Msisdn = msisdn,
|
|
Msisdn = msisdn,
|
|
|
UserId = account?.Id,
|
|
UserId = account?.Id,
|
|
|
ModeId = mode.ModeId,
|
|
ModeId = mode.ModeId,
|
|
|
- StartLevel = levelNo,
|
|
|
|
|
- CurrentLevel = levelNo,
|
|
|
|
|
- MaxLevelReached = levelNo,
|
|
|
|
|
|
|
+ StartLevel = levelNo.Value,
|
|
|
|
|
+ CurrentLevel = levelNo.Value,
|
|
|
|
|
+ MaxLevelReached = levelNo.Value,
|
|
|
TotalScore = 0,
|
|
TotalScore = 0,
|
|
|
TotalRewardScore = 0,
|
|
TotalRewardScore = 0,
|
|
|
TotalDataMb = 0,
|
|
TotalDataMb = 0,
|
|
@@ -633,7 +644,7 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
session.TurnDeducted = true;
|
|
session.TurnDeducted = true;
|
|
|
- session.TurnSource = turn.ChannelAdd;
|
|
|
|
|
|
|
+ session.TurnSource = ToTurnSource(turn.ChannelAdd);
|
|
|
session.TurnRefId = turn.Id.ToString(CultureInfo.InvariantCulture);
|
|
session.TurnRefId = turn.Id.ToString(CultureInfo.InvariantCulture);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -685,9 +696,13 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var now = DateTime.Now;
|
|
var now = DateTime.Now;
|
|
|
- var levelNo = (byte)Math.Clamp(request.level <= 0 ? 1 : request.level, 1, 255);
|
|
|
|
|
var mode = await GetGameModeAsync(request.gameType);
|
|
var mode = await GetGameModeAsync(request.gameType);
|
|
|
if (mode == null) return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game mode is not configured", new { });
|
|
if (mode == null) return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game mode is not configured", new { });
|
|
|
|
|
+ var levelNo = NormalizeRequestedLevel(request.level);
|
|
|
|
|
+ if (levelNo == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game level is not configured", new { });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
var account = await dbContext.AccountUsers.AsNoTracking()
|
|
var account = await dbContext.AccountUsers.AsNoTracking()
|
|
|
.Where(x => x.Msisdn == msisdn)
|
|
.Where(x => x.Msisdn == msisdn)
|
|
@@ -727,25 +742,35 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var levelConfig = await dbContext.GameLevelConfigs.AsNoTracking()
|
|
var levelConfig = await dbContext.GameLevelConfigs.AsNoTracking()
|
|
|
- .Where(x => x.ModeId == mode.ModeId && x.LevelNo == levelNo && x.Status != false)
|
|
|
|
|
|
|
+ .Where(x => x.ModeId == mode.ModeId && x.LevelNo == levelNo.Value && x.Status != false)
|
|
|
.FirstOrDefaultAsync();
|
|
.FirstOrDefaultAsync();
|
|
|
|
|
+ if (levelConfig == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game level is not configured", new { });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var clientScore = request.score < 0 ? 0 : request.score;
|
|
|
|
|
|
|
+ var clientScore = ClampScore(request.score, MaxAttemptScore);
|
|
|
var awardedScore = clientScore > session.TotalScore
|
|
var awardedScore = clientScore > session.TotalScore
|
|
|
? clientScore - session.TotalScore
|
|
? clientScore - session.TotalScore
|
|
|
: clientScore;
|
|
: clientScore;
|
|
|
- if (awardedScore <= 0) awardedScore = levelConfig?.BaseScore ?? 0;
|
|
|
|
|
|
|
+ if (awardedScore <= 0) awardedScore = levelConfig.BaseScore;
|
|
|
|
|
+ awardedScore = Math.Min(awardedScore, MaxAttemptScore);
|
|
|
|
|
|
|
|
// Chon cau hinh thuong cho mode/level, uu tien default roi den weight cao.
|
|
// Chon cau hinh thuong cho mode/level, uu tien default roi den weight cao.
|
|
|
var rewardOption = await dbContext.GameLevelRewardOptions.AsNoTracking()
|
|
var rewardOption = await dbContext.GameLevelRewardOptions.AsNoTracking()
|
|
|
- .Where(x => x.ModeId == mode.ModeId && x.LevelNo == levelNo && x.Status != false)
|
|
|
|
|
|
|
+ .Where(x => x.ModeId == mode.ModeId && x.LevelNo == levelNo.Value && x.Status != false)
|
|
|
.OrderByDescending(x => x.IsDefault == true)
|
|
.OrderByDescending(x => x.IsDefault == true)
|
|
|
.ThenByDescending(x => x.Weight)
|
|
.ThenByDescending(x => x.Weight)
|
|
|
.ThenBy(x => x.RewardOptionId)
|
|
.ThenBy(x => x.RewardOptionId)
|
|
|
.FirstOrDefaultAsync();
|
|
.FirstOrDefaultAsync();
|
|
|
|
|
+ if (rewardOption == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Level da cau hinh de choi nhung chua co giai, khong ghi history bi trong prize.
|
|
|
|
|
+ return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, "Game reward is not configured", new { });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
var attemptNo = ((await dbContext.GameLevelAttempts.AsNoTracking()
|
|
var attemptNo = ((await dbContext.GameLevelAttempts.AsNoTracking()
|
|
|
- .Where(x => x.SessionId == session.SessionId && x.LevelNo == levelNo)
|
|
|
|
|
|
|
+ .Where(x => x.SessionId == session.SessionId && x.LevelNo == levelNo.Value)
|
|
|
.MaxAsync(x => (short?)x.AttemptNo)) ?? 0) + 1;
|
|
.MaxAsync(x => (short?)x.AttemptNo)) ?? 0) + 1;
|
|
|
var childTransId = $"{request.transId}-{levelNo}-{attemptNo}";
|
|
var childTransId = $"{request.transId}-{levelNo}-{attemptNo}";
|
|
|
|
|
|
|
@@ -757,9 +782,9 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
ChildTransId = childTransId,
|
|
ChildTransId = childTransId,
|
|
|
Msisdn = msisdn,
|
|
Msisdn = msisdn,
|
|
|
ModeId = mode.ModeId,
|
|
ModeId = mode.ModeId,
|
|
|
- LevelNo = levelNo,
|
|
|
|
|
|
|
+ LevelNo = levelNo.Value,
|
|
|
AttemptNo = (short)attemptNo,
|
|
AttemptNo = (short)attemptNo,
|
|
|
- ExpectedScore = levelConfig?.BaseScore ?? 0,
|
|
|
|
|
|
|
+ ExpectedScore = levelConfig.BaseScore,
|
|
|
ClientScore = (int)Math.Min(int.MaxValue, clientScore),
|
|
ClientScore = (int)Math.Min(int.MaxValue, clientScore),
|
|
|
AwardedScore = (int)Math.Min(int.MaxValue, awardedScore),
|
|
AwardedScore = (int)Math.Min(int.MaxValue, awardedScore),
|
|
|
RewardOptionId = rewardOption?.RewardOptionId,
|
|
RewardOptionId = rewardOption?.RewardOptionId,
|
|
@@ -776,9 +801,9 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
};
|
|
};
|
|
|
dbContext.GameLevelAttempts.Add(attempt);
|
|
dbContext.GameLevelAttempts.Add(attempt);
|
|
|
|
|
|
|
|
- session.CurrentLevel = levelNo;
|
|
|
|
|
- session.MaxLevelReached = Math.Max(session.MaxLevelReached, levelNo);
|
|
|
|
|
- session.TotalScore += awardedScore;
|
|
|
|
|
|
|
+ session.CurrentLevel = levelNo.Value;
|
|
|
|
|
+ session.MaxLevelReached = Math.Max(session.MaxLevelReached, levelNo.Value);
|
|
|
|
|
+ session.TotalScore = ClampScore(session.TotalScore + awardedScore, MaxTotalScore);
|
|
|
session.LastActionTime = now;
|
|
session.LastActionTime = now;
|
|
|
session.UpdatedTime = now;
|
|
session.UpdatedTime = now;
|
|
|
|
|
|
|
@@ -791,7 +816,7 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
AttemptId = attempt.AttemptId,
|
|
AttemptId = attempt.AttemptId,
|
|
|
RootTransId = session.RootTransId,
|
|
RootTransId = session.RootTransId,
|
|
|
ChildTransId = childTransId,
|
|
ChildTransId = childTransId,
|
|
|
- ScoreType = "PLAY",
|
|
|
|
|
|
|
+ ScoreType = "LEVEL",
|
|
|
ScoreDelta = awardedScore,
|
|
ScoreDelta = awardedScore,
|
|
|
BalanceAfter = session.TotalScore,
|
|
BalanceAfter = session.TotalScore,
|
|
|
Description = $"Passed level {levelNo}",
|
|
Description = $"Passed level {levelNo}",
|
|
@@ -809,8 +834,26 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
{
|
|
{
|
|
|
rewardScoreDelta = (long)rewardOption.RewardValue;
|
|
rewardScoreDelta = (long)rewardOption.RewardValue;
|
|
|
session.TotalRewardScore += rewardScoreDelta;
|
|
session.TotalRewardScore += rewardScoreDelta;
|
|
|
- session.TotalScore += rewardScoreDelta;
|
|
|
|
|
|
|
+ session.TotalScore = ClampScore(session.TotalScore + rewardScoreDelta, MaxTotalScore);
|
|
|
responsePoint = FormatPrizeValue(rewardOption.RewardValue);
|
|
responsePoint = FormatPrizeValue(rewardOption.RewardValue);
|
|
|
|
|
+
|
|
|
|
|
+ dbContext.GameScoreLedgers.Add(new GameScoreLedger
|
|
|
|
|
+ {
|
|
|
|
|
+ LedgerId = await NextDecimalIdAsync("LEDGER"),
|
|
|
|
|
+ Msisdn = msisdn,
|
|
|
|
|
+ UserId = account?.Id,
|
|
|
|
|
+ SessionId = session.SessionId,
|
|
|
|
|
+ AttemptId = attempt.AttemptId,
|
|
|
|
|
+ RootTransId = session.RootTransId,
|
|
|
|
|
+ ChildTransId = childTransId,
|
|
|
|
|
+ ScoreType = "REWARD",
|
|
|
|
|
+ ScoreDelta = rewardScoreDelta,
|
|
|
|
|
+ BalanceAfter = session.TotalScore,
|
|
|
|
|
+ Description = $"Reward score for level {levelNo}",
|
|
|
|
|
+ CreatedTime = now,
|
|
|
|
|
+ CreatedDateKey = ToDateKey(now),
|
|
|
|
|
+ CreatedMonthKey = ToMonthKey(now)
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
else if (string.Equals(rewardOption.RewardType, "DATA", StringComparison.OrdinalIgnoreCase))
|
|
else if (string.Equals(rewardOption.RewardType, "DATA", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
{
|
|
@@ -833,7 +876,7 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
RootTransId = session.RootTransId,
|
|
RootTransId = session.RootTransId,
|
|
|
ChildTransId = childTransId,
|
|
ChildTransId = childTransId,
|
|
|
ModeId = mode.ModeId,
|
|
ModeId = mode.ModeId,
|
|
|
- LevelNo = levelNo,
|
|
|
|
|
|
|
+ LevelNo = levelNo.Value,
|
|
|
RewardOptionId = rewardOption.RewardOptionId,
|
|
RewardOptionId = rewardOption.RewardOptionId,
|
|
|
RewardType = rewardOption.RewardType,
|
|
RewardType = rewardOption.RewardType,
|
|
|
RewardValue = rewardOption.RewardValue,
|
|
RewardValue = rewardOption.RewardValue,
|
|
@@ -891,6 +934,21 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
return await DbLogic.GenIdAsync(dbContext, sequence) ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
return await DbLogic.GenIdAsync(dbContext, sequence) ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private static byte? NormalizeRequestedLevel(int level)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Level thuc te phai ton tai trong cau hinh DB, 50 chi la gioi han constraint DB.
|
|
|
|
|
+ var requestedLevel = level <= 0 ? MinGameLevel : level;
|
|
|
|
|
+ if (requestedLevel < MinGameLevel || requestedLevel > DbMaxGameLevel) return null;
|
|
|
|
|
+ return (byte)requestedLevel;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static long ClampScore(long score, long maxScore)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Diem tu client khong duoc vuot precision NUMBER cua bang game.
|
|
|
|
|
+ if (score < 0) return 0;
|
|
|
|
|
+ return score > maxScore ? maxScore : score;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private async Task<GameMode?> GetGameModeAsync(string? gameType)
|
|
private async Task<GameMode?> GetGameModeAsync(string? gameType)
|
|
|
{
|
|
{
|
|
|
var modeCode = (gameType ?? "EASY").Trim().ToUpperInvariant();
|
|
var modeCode = (gameType ?? "EASY").Trim().ToUpperInvariant();
|
|
@@ -907,6 +965,13 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
.FirstOrDefaultAsync();
|
|
.FirstOrDefaultAsync();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private async Task<bool> IsConfiguredLevelAsync(decimal modeId, byte levelNo)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Moi loai game co so level rieng, lay theo GAME_LEVEL_CONFIG dang active.
|
|
|
|
|
+ return await dbContext.GameLevelConfigs.AsNoTracking()
|
|
|
|
|
+ .AnyAsync(x => x.ModeId == modeId && x.LevelNo == levelNo && x.Status != false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private async Task<int> GetAvailableTurnAsync(string msisdn, DateTime now)
|
|
private async Task<int> GetAvailableTurnAsync(string msisdn, DateTime now)
|
|
|
{
|
|
{
|
|
|
var total = await dbContext.LuckySpins.AsNoTracking()
|
|
var total = await dbContext.LuckySpins.AsNoTracking()
|
|
@@ -983,6 +1048,17 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
return value.Length <= maxLength ? value : value[..maxLength];
|
|
return value.Length <= maxLength ? value : value[..maxLength];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private static string? ToTurnSource(string? channelAdd)
|
|
|
|
|
+ {
|
|
|
|
|
+ var value = (channelAdd ?? "").Trim().ToUpperInvariant();
|
|
|
|
|
+ if (value.Contains("SUB") || value.Contains("REG")) return "SUB";
|
|
|
|
|
+ if (value.Contains("BUY") || value.Contains("CHARGE")) return "BUY";
|
|
|
|
|
+ if (value.Contains("FREE")) return "FREE";
|
|
|
|
|
+ if (value.Contains("GIFT")) return "GIFT";
|
|
|
|
|
+ if (value.Contains("ADMIN")) return "ADMIN";
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private static DateTime? ParseHistoryDate(string? value)
|
|
private static DateTime? ParseHistoryDate(string? value)
|
|
|
{
|
|
{
|
|
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
@@ -1046,13 +1122,6 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
private string ToLocalMsisdn(string? msisdn)
|
|
private string ToLocalMsisdn(string? msisdn)
|
|
|
{
|
|
{
|
|
|
var value = new string((msisdn ?? "").Where(char.IsDigit).ToArray());
|
|
var value = new string((msisdn ?? "").Where(char.IsDigit).ToArray());
|
|
|
- var countryCode = new string(GetParameter("CountryCode").Where(char.IsDigit).ToArray());
|
|
|
|
|
-
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(countryCode) && value.StartsWith(countryCode, StringComparison.Ordinal))
|
|
|
|
|
- {
|
|
|
|
|
- value = "0" + value[countryCode.Length..];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
return value;
|
|
return value;
|
|
|
}
|
|
}
|
|
|
|
|
|